summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp109
-rw-r--r--Android.bp14
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--android-sdk-flags/Android.bp13
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig2
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java13
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java12
-rw-r--r--api/Android.bp15
-rw-r--r--api/api.go3
-rw-r--r--boot/Android.bp10
-rw-r--r--cmds/idmap2/Android.bp1
-rw-r--r--cmds/idmap2/libidmap2/ResourceContainer.cpp24
-rw-r--r--cmds/idmap2/tests/IdmapTests.cpp14
-rw-r--r--cmds/idmap2/tests/data/target/target-bad.apkbin0 -> 821 bytes
-rw-r--r--core/api/current.txt315
-rw-r--r--core/api/lint-baseline.txt10
-rw-r--r--core/api/module-lib-current.txt41
-rw-r--r--core/api/system-current.txt400
-rw-r--r--core/api/test-current.txt21
-rw-r--r--core/api/test-lint-baseline.txt6
-rw-r--r--core/java/android/animation/Animator.java8
-rw-r--r--core/java/android/app/Activity.java19
-rw-r--r--core/java/android/app/ActivityManager.java12
-rw-r--r--core/java/android/app/ActivityManagerInternal.java10
-rw-r--r--core/java/android/app/ActivityThread.java115
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java12
-rw-r--r--core/java/android/app/AppOpsManager.aidl1
-rw-r--r--core/java/android/app/AppOpsManager.java241
-rw-r--r--core/java/android/app/AppOpsManagerInternal.java8
-rw-r--r--core/java/android/app/ApplicationPackageManager.java2
-rw-r--r--core/java/android/app/BroadcastStickyCache.java215
-rw-r--r--core/java/android/app/CameraCompatTaskInfo.java26
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/app/IActivityManager.aidl12
-rw-r--r--core/java/android/app/IApplicationThread.aidl4
-rw-r--r--core/java/android/app/Notification.java155
-rw-r--r--core/java/android/app/ProfilerInfo.java6
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java44
-rw-r--r--core/java/android/app/ResourcesManager.java4
-rw-r--r--core/java/android/app/SystemServiceRegistry.java29
-rw-r--r--core/java/android/app/TEST_MAPPING4
-rw-r--r--core/java/android/app/TaskInfo.java12
-rw-r--r--core/java/android/app/WallpaperManager.java44
-rw-r--r--core/java/android/app/activity_manager.aconfig18
-rw-r--r--core/java/android/app/admin/DevicePolicyIdentifiers.java7
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java123
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl8
-rw-r--r--core/java/android/app/admin/UnknownAuthority.java35
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig23
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.java19
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java21
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java20
-rw-r--r--core/java/android/app/appfunctions/GenericDocumentWrapper.java21
-rw-r--r--core/java/android/app/assist/AssistContent.java25
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig5
-rw-r--r--core/java/android/app/jank/flags.aconfig1
-rw-r--r--core/java/android/app/notification.aconfig7
-rw-r--r--core/java/android/app/ondeviceintelligence/OWNERS6
-rw-r--r--core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig17
-rw-r--r--core/java/android/app/performance.aconfig8
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java2
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java2
-rw-r--r--core/java/android/app/servertransaction/ConfigurationChangeItem.java2
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java4
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java2
-rw-r--r--core/java/android/app/supervision/SupervisionManagerInternal.java31
-rw-r--r--core/java/android/app/supervision/flags.aconfig16
-rw-r--r--core/java/android/app/wallpaper/WallpaperDescription.java154
-rw-r--r--core/java/android/appwidget/flags.aconfig1
-rw-r--r--core/java/android/companion/AssociationInfo.java64
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java65
-rw-r--r--core/java/android/companion/CompanionDeviceService.java8
-rw-r--r--core/java/android/companion/DeviceId.aidl19
-rw-r--r--core/java/android/companion/DeviceId.java214
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl5
-rw-r--r--core/java/android/companion/flags.aconfig1
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl6
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl7
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceInternal.java18
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java11
-rw-r--r--core/java/android/content/ClipData.java28
-rw-r--r--core/java/android/content/Context.java6
-rw-r--r--core/java/android/content/Intent.java83
-rw-r--r--core/java/android/content/pm/PackageManager.java88
-rw-r--r--core/java/android/content/pm/SigningInfo.java66
-rw-r--r--core/java/android/content/pm/SigningInfoException.java50
-rw-r--r--core/java/android/content/pm/TEST_MAPPING11
-rw-r--r--core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java7
-rw-r--r--core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl4
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig12
-rw-r--r--core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java30
-rw-r--r--core/java/android/content/pm/xr.aconfig1
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java166
-rw-r--r--core/java/android/credentials/flags.aconfig20
-rw-r--r--core/java/android/hardware/biometrics/flags.aconfig1
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java23
-rw-r--r--core/java/android/hardware/camera2/TotalCaptureResult.java6
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java68
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java12
-rw-r--r--core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java5
-rw-r--r--core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java14
-rw-r--r--core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java19
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java21
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSession.java19
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpoint.aidl3
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl4
-rw-r--r--core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java6
-rw-r--r--core/java/android/hardware/display/DisplayManager.java26
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java126
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java322
-rw-r--r--core/java/android/hardware/display/DisplayTopologyGraph.java43
-rw-r--r--core/java/android/hardware/display/IBrightnessListener.aidl33
-rw-r--r--core/java/android/hardware/display/IDisplayManagerCallback.aidl3
-rw-r--r--core/java/android/hardware/display/IVirtualDisplayCallback.aidl5
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java22
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java153
-rw-r--r--core/java/android/hardware/input/InputSettings.java48
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java2
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig15
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java16
-rw-r--r--core/java/android/hardware/soundtrigger/ConversionUtil.java4
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java42
-rw-r--r--core/java/android/hardware/usb/flags/usb_framework_flags.aconfig1
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java7
-rw-r--r--core/java/android/net/EventLogTags.logtags2
-rw-r--r--core/java/android/net/LocalSocket.java2
-rw-r--r--core/java/android/net/flags.aconfig9
-rw-r--r--core/java/android/net/http/X509TrustManagerExtensions.java88
-rw-r--r--core/java/android/net/metrics/DnsEvent.java6
-rw-r--r--core/java/android/os/Build.java66
-rw-r--r--core/java/android/os/Bundle.java24
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java313
-rw-r--r--core/java/android/os/ConcurrentMessageQueue/MessageQueue.java156
-rw-r--r--core/java/android/os/CpuHeadroomParams.java36
-rw-r--r--core/java/android/os/CpuHeadroomParamsInternal.aidl1
-rw-r--r--core/java/android/os/GpuHeadroomParams.java35
-rw-r--r--core/java/android/os/GraphicsEnvironment.java5
-rw-r--r--core/java/android/os/IHintManager.aidl6
-rw-r--r--core/java/android/os/IHintSession.aidl5
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/IpcDataCache.java66
-rw-r--r--core/java/android/os/LegacyMessageQueue/MessageQueue.java133
-rw-r--r--core/java/android/os/OWNERS8
-rw-r--r--core/java/android/os/Parcel.java31
-rw-r--r--core/java/android/os/PowerManager.java12
-rw-r--r--core/java/android/os/SELinux.java27
-rw-r--r--core/java/android/os/ServiceManager.java6
-rw-r--r--core/java/android/os/ServiceManagerNative.java2
-rw-r--r--core/java/android/os/SessionCreationConfig.aidl8
-rw-r--r--core/java/android/os/TestLooperManager.java47
-rw-r--r--core/java/android/os/UpdateEngine.java20
-rw-r--r--core/java/android/os/UserManager.java2
-rw-r--r--core/java/android/os/flags.aconfig18
-rw-r--r--core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl7
-rw-r--r--core/java/android/os/instrumentation/IOffsetCallback.aidl28
-rw-r--r--core/java/android/os/instrumentation/MethodDescriptorParser.java82
-rw-r--r--core/java/android/permission/PermissionManager.java39
-rw-r--r--core/java/android/permission/flags.aconfig47
-rw-r--r--core/java/android/preference/PreferenceActivity.java41
-rw-r--r--core/java/android/provider/Settings.java43
-rw-r--r--core/java/android/security/advancedprotection/AdvancedProtectionManager.java25
-rw-r--r--core/java/android/security/advancedprotection/OWNERS1
-rw-r--r--core/java/android/security/flags.aconfig3
-rw-r--r--core/java/android/security/net/config/CertificatesEntryRef.java10
-rw-r--r--core/java/android/security/net/config/KeyStoreConfigSource.java4
-rw-r--r--core/java/android/security/net/config/NetworkSecurityConfig.java22
-rw-r--r--core/java/android/security/net/config/XmlConfigSource.java5
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig8
-rw-r--r--core/java/android/service/autofill/AutofillService.java47
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java31
-rw-r--r--core/java/android/service/autofill/IAutoFillService.aidl3
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java1
-rw-r--r--core/java/android/service/ondeviceintelligence/OWNERS1
-rw-r--r--core/java/android/service/quickaccesswallet/flags.aconfig1
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java39
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java2
-rw-r--r--core/java/android/service/voice/OWNERS2
-rw-r--r--core/java/android/speech/tts/EventLogTags.logtags2
-rw-r--r--core/java/android/telephony/TelephonyCallback.java30
-rw-r--r--core/java/android/text/flags/flags.aconfig4
-rw-r--r--core/java/android/util/Log.java2
-rw-r--r--core/java/android/view/Display.java23
-rw-r--r--core/java/android/view/DisplayEventReceiver.java17
-rw-r--r--core/java/android/view/DisplayInfo.java12
-rw-r--r--core/java/android/view/IDisplayWindowInsetsController.aidl2
-rw-r--r--core/java/android/view/IWindowManager.aidl4
-rw-r--r--core/java/android/view/InsetsSource.java25
-rw-r--r--core/java/android/view/LetterboxScrollProcessor.java5
-rw-r--r--core/java/android/view/Surface.java26
-rw-r--r--core/java/android/view/View.java30
-rw-r--r--core/java/android/view/ViewConfiguration.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java13
-rw-r--r--core/java/android/view/ViewStructure.java12
-rw-r--r--core/java/android/view/WindowManager.java75
-rw-r--r--core/java/android/view/WindowManagerImpl.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java14
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig17
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java4
-rw-r--r--core/java/android/view/autofill/AutofillManager.java3
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig1
-rw-r--r--core/java/android/view/flags/scroll_capture.aconfig2
-rw-r--r--core/java/android/view/flags/scroll_feedback_flags.aconfig6
-rw-r--r--core/java/android/view/flags/view_flags.aconfig12
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java8
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java13
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig2
-rw-r--r--core/java/android/webkit/EventLogTags.logtags2
-rw-r--r--core/java/android/webkit/UserPackage.java11
-rw-r--r--core/java/android/webkit/WebViewFactory.java10
-rw-r--r--core/java/android/webkit/WebViewFactoryProvider.java58
-rw-r--r--core/java/android/webkit/flags.aconfig8
-rw-r--r--core/java/android/widget/AbsListView.java6
-rw-r--r--core/java/android/widget/Button.java4
-rw-r--r--core/java/android/widget/DateTimeView.java227
-rw-r--r--core/java/android/widget/RemoteViews.java5
-rw-r--r--core/java/android/widget/ScrollView.java4
-rw-r--r--core/java/android/widget/TextView.java598
-rw-r--r--core/java/android/window/DesktopModeFlags.java13
-rw-r--r--core/java/android/window/KeyguardState.java33
-rw-r--r--core/java/android/window/SystemPerformanceHinter.java6
-rw-r--r--core/java/android/window/TransitionRequestInfo.java199
-rw-r--r--core/java/android/window/WindowTokenClient.java2
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig12
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig51
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig3
-rw-r--r--core/java/android/window/flags/wallpaper_manager.aconfig1
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig12
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig12
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java88
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java4
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java18
-rw-r--r--core/java/com/android/internal/app/AlertController.java38
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl1
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java5
-rw-r--r--core/java/com/android/internal/app/chooser/DisplayResolveInfo.java2
-rw-r--r--core/java/com/android/internal/app/chooser/SelectableTargetInfo.java1
-rw-r--r--core/java/com/android/internal/app/chooser/TargetInfo.java20
-rw-r--r--core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java18
-rw-r--r--core/java/com/android/internal/jank/EventLogTags.logtags2
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java8
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java45
-rw-r--r--core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java424
-rw-r--r--core/java/com/android/internal/os/TEST_MAPPING2
-rw-r--r--core/java/com/android/internal/os/Zygote.java3
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java20
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java22
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java16
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl7
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java68
-rw-r--r--core/java/com/android/internal/util/LatencyTracker.java8
-rw-r--r--core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java184
-rw-r--r--core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java182
-rw-r--r--core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java40
-rw-r--r--core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java57
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlConstants.java8
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlReader.java66
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java3
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java10
-rw-r--r--core/java/com/android/internal/widget/NotificationActionListLayout.java7
-rw-r--r--core/java/com/android/internal/widget/flags.aconfig9
-rw-r--r--core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java86
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java208
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java210
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java46
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java167
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java33
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java100
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java25
-rw-r--r--core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java44
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java112
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operation.java28
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java52
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java44
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java22
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/TouchListener.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java59
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java69
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java18
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java5
-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.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Header.java7
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java24
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java76
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java38
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java98
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java83
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java58
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java16
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java15
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java35
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java36
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java48
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java214
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java192
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java107
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java143
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java28
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java23
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java34
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java73
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java157
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java47
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java48
-rw-r--r--core/jni/Android.bp9
-rw-r--r--core/jni/AndroidRuntime.cpp4
-rw-r--r--core/jni/android_hardware_camera2_CameraDevice.cpp148
-rw-r--r--core/jni/android_hardware_display_DisplayTopology.cpp155
-rw-r--r--core/jni/android_hardware_display_DisplayTopology.h32
-rw-r--r--core/jni/android_media_AudioFormat.h60
-rw-r--r--core/jni/android_os_SELinux.cpp30
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp17
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp7
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp8
-rw-r--r--core/jni/com_android_internal_util_ArrayUtils.cpp119
-rw-r--r--core/proto/android/providers/settings/system.proto1
-rw-r--r--core/res/Android.bp2
-rw-r--r--core/res/AndroidManifest.xml42
-rw-r--r--core/res/res/color-watch-v36/btn_material_outlined_background_color.xml22
-rw-r--r--core/res/res/drawable-watch-v36/btn_background_material_outlined.xml39
-rw-r--r--core/res/res/drawable-watch-v36/btn_background_material_text.xml28
-rw-r--r--core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml43
-rw-r--r--core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml (renamed from core/res/res/layout-watch-v36/alert_dialog_material.xml)0
-rw-r--r--core/res/res/layout/notification_2025_conversation_face_pile_layout.xml54
-rw-r--r--core/res/res/layout/notification_2025_conversation_icon_container.xml104
-rw-r--r--core/res/res/layout/notification_2025_messaging_group.xml80
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml16
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml79
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml14
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml14
-rw-r--r--core/res/res/layout/notification_2025_template_conversation.xml159
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_picture.xml93
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_text.xml94
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml109
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_inbox.xml131
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_media.xml103
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml71
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_progress.xml122
-rw-r--r--core/res/res/layout/notification_2025_template_header.xml6
-rw-r--r--core/res/res/layout/side_fps_toast.xml4
-rw-r--r--core/res/res/values-af/strings.xml148
-rw-r--r--core/res/res/values-am/strings.xml36
-rw-r--r--core/res/res/values-ar/strings.xml36
-rw-r--r--core/res/res/values-as/strings.xml36
-rw-r--r--core/res/res/values-az/strings.xml38
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml36
-rw-r--r--core/res/res/values-be/strings.xml36
-rw-r--r--core/res/res/values-bg/strings.xml36
-rw-r--r--core/res/res/values-bn/strings.xml36
-rw-r--r--core/res/res/values-bs/strings.xml36
-rw-r--r--core/res/res/values-ca/strings.xml36
-rw-r--r--core/res/res/values-cs/strings.xml36
-rw-r--r--core/res/res/values-da/strings.xml36
-rw-r--r--core/res/res/values-de/strings.xml36
-rw-r--r--core/res/res/values-el/strings.xml36
-rw-r--r--core/res/res/values-en-rAU/strings.xml36
-rw-r--r--core/res/res/values-en-rCA/strings.xml36
-rw-r--r--core/res/res/values-en-rGB/strings.xml36
-rw-r--r--core/res/res/values-en-rIN/strings.xml36
-rw-r--r--core/res/res/values-es-rUS/strings.xml36
-rw-r--r--core/res/res/values-es/strings.xml36
-rw-r--r--core/res/res/values-et/strings.xml36
-rw-r--r--core/res/res/values-eu/strings.xml44
-rw-r--r--core/res/res/values-fa/strings.xml36
-rw-r--r--core/res/res/values-fi/strings.xml36
-rw-r--r--core/res/res/values-fr-rCA/strings.xml36
-rw-r--r--core/res/res/values-fr/strings.xml36
-rw-r--r--core/res/res/values-gl/strings.xml36
-rw-r--r--core/res/res/values-gu/strings.xml36
-rw-r--r--core/res/res/values-hi/strings.xml36
-rw-r--r--core/res/res/values-hr/strings.xml36
-rw-r--r--core/res/res/values-hu/strings.xml36
-rw-r--r--core/res/res/values-hy/strings.xml36
-rw-r--r--core/res/res/values-in/strings.xml36
-rw-r--r--core/res/res/values-is/strings.xml36
-rw-r--r--core/res/res/values-it/strings.xml52
-rw-r--r--core/res/res/values-iw/strings.xml36
-rw-r--r--core/res/res/values-ja/strings.xml36
-rw-r--r--core/res/res/values-ka/strings.xml36
-rw-r--r--core/res/res/values-kk/strings.xml44
-rw-r--r--core/res/res/values-km/strings.xml38
-rw-r--r--core/res/res/values-kn/strings.xml38
-rw-r--r--core/res/res/values-ko/strings.xml36
-rw-r--r--core/res/res/values-ky/strings.xml36
-rw-r--r--core/res/res/values-lo/strings.xml36
-rw-r--r--core/res/res/values-lt/strings.xml36
-rw-r--r--core/res/res/values-lv/strings.xml36
-rw-r--r--core/res/res/values-mk/strings.xml36
-rw-r--r--core/res/res/values-ml/strings.xml36
-rw-r--r--core/res/res/values-mn/strings.xml36
-rw-r--r--core/res/res/values-mr/strings.xml36
-rw-r--r--core/res/res/values-ms/strings.xml36
-rw-r--r--core/res/res/values-my/strings.xml36
-rw-r--r--core/res/res/values-nb/strings.xml36
-rw-r--r--core/res/res/values-ne/strings.xml38
-rw-r--r--core/res/res/values-nl/strings.xml36
-rw-r--r--core/res/res/values-or/strings.xml38
-rw-r--r--core/res/res/values-pa/strings.xml36
-rw-r--r--core/res/res/values-pl/strings.xml36
-rw-r--r--core/res/res/values-pt-rBR/strings.xml36
-rw-r--r--core/res/res/values-pt-rPT/strings.xml36
-rw-r--r--core/res/res/values-pt/strings.xml36
-rw-r--r--core/res/res/values-ro/strings.xml36
-rw-r--r--core/res/res/values-ru/strings.xml36
-rw-r--r--core/res/res/values-si/strings.xml38
-rw-r--r--core/res/res/values-sk/strings.xml36
-rw-r--r--core/res/res/values-sl/strings.xml36
-rw-r--r--core/res/res/values-sq/strings.xml38
-rw-r--r--core/res/res/values-sr/strings.xml36
-rw-r--r--core/res/res/values-sv/strings.xml36
-rw-r--r--core/res/res/values-sw/strings.xml36
-rw-r--r--core/res/res/values-ta/strings.xml36
-rw-r--r--core/res/res/values-te/strings.xml40
-rw-r--r--core/res/res/values-th/strings.xml36
-rw-r--r--core/res/res/values-tl/strings.xml36
-rw-r--r--core/res/res/values-tr/strings.xml36
-rw-r--r--core/res/res/values-uk/strings.xml36
-rw-r--r--core/res/res/values-ur/strings.xml36
-rw-r--r--core/res/res/values-uz/strings.xml36
-rw-r--r--core/res/res/values-vi/strings.xml36
-rw-r--r--core/res/res/values-watch-v36/dimens_material.xml5
-rw-r--r--core/res/res/values-watch-v36/styles_material.xml31
-rw-r--r--core/res/res/values-watch/config_material.xml46
-rw-r--r--core/res/res/values-zh-rCN/strings.xml36
-rw-r--r--core/res/res/values-zh-rHK/strings.xml36
-rw-r--r--core/res/res/values-zh-rTW/strings.xml36
-rw-r--r--core/res/res/values-zu/strings.xml36
-rw-r--r--core/res/res/values/attrs.xml26
-rw-r--r--core/res/res/values/attrs_manifest.xml2
-rw-r--r--core/res/res/values/config.xml39
-rw-r--r--core/res/res/values/config_material.xml47
-rw-r--r--core/res/res/values/config_telephony.xml10
-rw-r--r--core/res/res/values/dimens.xml29
-rw-r--r--core/res/res/values/public-staging.xml66
-rw-r--r--core/res/res/values/strings.xml80
-rw-r--r--core/res/res/values/symbols.xml81
-rw-r--r--core/tests/FileSystemUtilsTest/Android.bp31
-rw-r--r--core/tests/FileSystemUtilsTest/AndroidTest.xml1
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml37
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java60
-rw-r--r--core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java67
-rw-r--r--core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp6
-rw-r--r--core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java9
-rw-r--r--core/tests/coretests/Android.bp15
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java17
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java15
-rw-r--r--core/tests/coretests/src/android/app/QueuedWorkTest.java22
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java61
-rw-r--r--core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java180
-rw-r--r--core/tests/coretests/src/android/content/IntentTest.java113
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTest.java24
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt13
-rw-r--r--core/tests/coretests/src/android/content/res/ResourcesManagerTest.java10
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java98
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt340
-rw-r--r--core/tests/coretests/src/android/os/BinderProxyTest.java13
-rw-r--r--core/tests/coretests/src/android/os/BinderThreadPriorityTest.java4
-rw-r--r--core/tests/coretests/src/android/os/BuildTest.java97
-rw-r--r--core/tests/coretests/src/android/os/IpcDataCacheTest.java82
-rw-r--r--core/tests/coretests/src/android/os/MessageQueueTest.java255
-rw-r--r--core/tests/coretests/src/android/os/PowerManagerTest.java14
-rw-r--r--core/tests/coretests/src/android/os/TestLooperManagerTest.java91
-rw-r--r--core/tests/coretests/src/android/os/WorkDurationUnitTest.java13
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceTest.java96
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupTest.java42
-rw-r--r--core/tests/coretests/src/android/widget/DateTimeViewTest.java135
-rw-r--r--core/tests/coretests/src/android/window/OWNERS3
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java130
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java21
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java91
-rw-r--r--core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java54
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java492
-rw-r--r--core/xsd/vibrator/vibration/schema/current.txt38
-rw-r--r--core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd56
-rw-r--r--core/xsd/vibrator/vibration/vibration.xsd56
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--framework-jarjar-rules.txt1
-rw-r--r--graphics/java/android/graphics/Paint.java18
-rw-r--r--graphics/java/android/graphics/Path.java3
-rw-r--r--keystore/java/Android.bp8
-rw-r--r--keystore/java/android/security/KeyStore2.java14
-rw-r--r--keystore/java/android/security/KeyStore2HalCurrent.java30
-rw-r--r--keystore/java/android/security/KeyStore2HalLatest.java31
-rw-r--r--keystore/java/android/security/keystore/KeyStoreManager.java35
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java26
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt49
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt37
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt66
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt68
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt33
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt54
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt31
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt41
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt79
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt123
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml10
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml7
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java23
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java15
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java329
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt176
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java)12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt206
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt169
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt165
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt138
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt123
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt1024
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt127
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt153
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java160
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java206
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java241
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt118
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt40
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml2
-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/SwitchBackToSplitFromAnotherApp.kt1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt16
-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/Android.bp21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt93
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt76
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt173
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt92
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt146
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt103
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt51
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt62
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt151
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt128
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt244
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt119
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt126
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt231
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java326
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java168
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt170
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt129
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt170
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt144
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt17
-rw-r--r--libs/androidfw/ApkAssets.cpp25
-rw-r--r--libs/androidfw/CursorWindow.cpp16
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h58
-rw-r--r--libs/androidfw/tests/ApkAssets_test.cpp23
-rw-r--r--libs/androidfw/tests/data/bad/bad.apkbin0 -> 178 bytes
-rw-r--r--libs/appfunctions/api/current.txt1
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java10
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig3
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp9
-rw-r--r--libs/hwui/hwui/Typeface.cpp5
-rw-r--r--libs/hwui/hwui/Typeface.h3
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp26
-rw-r--r--libs/input/MouseCursorController.cpp70
-rw-r--r--libs/input/MouseCursorController.h16
-rw-r--r--libs/input/PointerController.cpp23
-rw-r--r--libs/input/PointerController.h9
-rw-r--r--libs/input/tests/PointerController_test.cpp131
-rw-r--r--location/api/system-current.txt8
-rw-r--r--location/java/android/location/flags/location.aconfig6
-rw-r--r--location/java/android/location/provider/IPopulationDensityProvider.aidl45
-rw-r--r--location/java/android/location/provider/IS2CellIdsCallback.aidl36
-rw-r--r--location/java/android/location/provider/IS2LevelCallback.aidl34
-rw-r--r--location/java/android/location/provider/PopulationDensityProviderBase.java194
-rw-r--r--media/java/android/media/AudioDeviceInfo.java40
-rw-r--r--media/java/android/media/AudioFormat.java299
-rw-r--r--media/java/android/media/AudioSystem.java24
-rw-r--r--media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl1
-rw-r--r--media/java/android/media/MediaCas.java10
-rw-r--r--media/java/android/media/MediaRoute2Info.java192
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java199
-rw-r--r--media/java/android/media/flags/projection.aconfig14
-rw-r--r--media/java/android/media/projection/MediaProjection.java13
-rw-r--r--media/java/android/media/quality/ActiveProcessingPicture.aidl19
-rw-r--r--media/java/android/media/quality/ActiveProcessingPicture.java86
-rw-r--r--media/java/android/media/quality/AmbientBacklightMetadata.java10
-rw-r--r--media/java/android/media/quality/IMediaQualityManager.aidl69
-rw-r--r--media/java/android/media/quality/IPictureProfileCallback.aidl2
-rw-r--r--media/java/android/media/quality/ISoundProfileCallback.aidl2
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java321
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java290
-rw-r--r--media/java/android/media/quality/PictureProfile.java31
-rw-r--r--media/java/android/media/quality/PictureProfileHandle.java14
-rw-r--r--media/java/android/media/quality/SoundProfile.java31
-rw-r--r--media/java/android/media/quality/SoundProfileHandle.aidl19
-rw-r--r--media/java/android/media/quality/SoundProfileHandle.java72
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java2
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig10
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java10
-rw-r--r--media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java14
-rw-r--r--media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl11
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java3
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java13
-rw-r--r--native/android/Android.bp1
-rw-r--r--native/android/OWNERS3
-rw-r--r--native/android/display_luts.cpp9
-rw-r--r--native/android/dynamic_instrumentation_manager.cpp39
-rw-r--r--native/android/libandroid.map.txt24
-rw-r--r--native/android/performance_hint.cpp186
-rw-r--r--native/android/surface_control.cpp24
-rw-r--r--native/android/system_health.cpp278
-rw-r--r--native/android/tests/performance_hint/PerformanceHintNativeTest.cpp7
-rw-r--r--native/android/tests/thermal/NativeThermalUnitTest.cpp303
-rw-r--r--native/android/thermal.cpp323
-rw-r--r--nfc/api/system-current.txt1
-rw-r--r--nfc/java/android/nfc/Entry.java10
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java12
-rw-r--r--nfc/java/android/nfc/NfcRoutingTableEntry.java16
-rw-r--r--nfc/java/android/nfc/RoutingTableAidEntry.java6
-rw-r--r--nfc/java/android/nfc/RoutingTableProtocolEntry.java6
-rw-r--r--nfc/java/android/nfc/RoutingTableSystemCodeEntry.java6
-rw-r--r--nfc/java/android/nfc/RoutingTableTechnologyEntry.java6
-rw-r--r--nfc/java/android/nfc/cardemulation/HostApduService.java2
-rw-r--r--nfc/java/android/nfc/cardemulation/OffHostApduService.java2
-rw-r--r--nfc/lint-baseline.xml211
-rw-r--r--nfc/tests/src/android/nfc/NdefRecordTest.java77
-rw-r--r--omapi/aidl/Android.bp1
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml1
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java131
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java4
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java13
-rw-r--r--packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java9
-rw-r--r--packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java4
-rw-r--r--packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java4
-rw-r--r--packages/CredentialManager/res/values-fr/strings.xml2
-rw-r--r--packages/CredentialManager/res/values-hy/strings.xml2
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransport.java14
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java19
-rw-r--r--packages/NeuralNetworks/framework/Android.bp30
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java (renamed from core/java/android/app/ondeviceintelligence/DownloadCallback.java)9
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl (renamed from core/java/android/app/ondeviceintelligence/Feature.aidl)2
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java (renamed from core/java/android/app/ondeviceintelligence/Feature.java)10
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl (renamed from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl)2
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java (renamed from core/java/android/app/ondeviceintelligence/FeatureDetails.java)18
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl24
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl (renamed from core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl)4
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl (renamed from core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl24
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IResponseCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl (renamed from core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl (renamed from core/java/android/app/ondeviceintelligence/InferenceInfo.aidl)2
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java (renamed from core/java/android/app/ondeviceintelligence/InferenceInfo.java)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java (renamed from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java)8
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java54
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java (renamed from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java)98
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java (renamed from core/java/android/app/ondeviceintelligence/ProcessingCallback.java)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java (renamed from core/java/android/app/ondeviceintelligence/ProcessingSignal.java)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java (renamed from core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl (renamed from core/java/android/app/ondeviceintelligence/TokenInfo.aidl)2
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java (renamed from core/java/android/app/ondeviceintelligence/TokenInfo.java)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java96
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl (renamed from core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl)4
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl (renamed from core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl)6
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl (renamed from core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl (renamed from core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl)0
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl (renamed from core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl)2
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java (renamed from core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java)282
-rw-r--r--packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java (renamed from core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java)229
-rw-r--r--packages/NeuralNetworks/service/Android.bp29
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java (renamed from services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java)24
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java (renamed from services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java)5
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java (renamed from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java)16
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java (renamed from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java)248
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java (renamed from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java)8
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java (renamed from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java)15
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java (renamed from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java)16
-rw-r--r--packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java (renamed from services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java)4
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml7
-rw-r--r--packages/PackageInstaller/TEST_MAPPING11
-rw-r--r--packages/PackageInstaller/res/values-af/strings.xml46
-rw-r--r--packages/PackageInstaller/res/values-it/strings.xml2
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-af/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-am/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-as/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-az/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-be/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-da/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-de/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-el/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-es/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-et/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-in/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-is/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-it/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-km/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-my/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-or/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-si/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-th/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml6
-rw-r--r--packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml6
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt61
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt55
-rw-r--r--packages/SettingsLib/Graph/graph.proto2
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt3
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java2
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt17
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt7
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt23
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt4
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt6
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt11
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml4
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml4
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml2
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml11
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml3
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-af/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-am/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-as/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-az/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-be/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-da/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-de/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-el/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-es/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-et/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-in/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-is/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-it/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-km/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-my/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-or/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-si/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-th/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml22
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip (renamed from packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip)bin136715430 -> 136920070 bytes
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts6
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt19
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt12
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt145
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt102
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt11
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt1
-rw-r--r--packages/SettingsLib/UsageProgressBarPreference/Android.bp1
-rw-r--r--packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java2
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig14
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume.xml44
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml31
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml31
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml43
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml31
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml43
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml47
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml43
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml47
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml51
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml31
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml43
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml20
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml14
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml14
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-uk/arrays.xml4
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java41
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java408
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java48
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java239
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java14
-rw-r--r--packages/SettingsProvider/res/xml/bookmarks.xml62
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java130
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java175
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java38
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig10
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java4
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java586
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java7
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java2
-rw-r--r--packages/Shell/Android.bp6
-rw-r--r--packages/Shell/AndroidManifest.xml4
-rw-r--r--packages/Shell/res/values/defaults.xml5
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java574
-rw-r--r--packages/SoundPicker/res/values-in/strings.xml2
-rw-r--r--packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt1
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt39
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt8
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt17
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt94
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt69
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt29
-rw-r--r--packages/SystemUI/AndroidManifest.xml7
-rw-r--r--packages/SystemUI/TEST_MAPPING8
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml5
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java15
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java5
-rw-r--r--packages/SystemUI/aconfig/biometrics_framework.aconfig7
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig72
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java106
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java17
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java87
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt56
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt41
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt50
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt162
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt451
-rw-r--r--packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt2
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt75
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt497
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt57
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt443
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt121
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt238
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt32
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt35
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt83
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt107
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt328
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt165
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt42
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt119
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt105
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt89
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt34
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt113
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt11
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt16
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt47
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt173
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt100
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt73
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt39
-rw-r--r--packages/SystemUI/compose/scene/tests/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt12
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt305
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt241
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt11
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt127
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt12
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt208
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt283
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt57
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt35
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt92
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt90
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt6
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt7
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt1
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt151
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt23
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt20
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt4
-rw-r--r--packages/SystemUI/lint-baseline.xml1176
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling)36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java187
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable)12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt80
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt327
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt241
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt118
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt121
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt195
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt167
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt179
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt96
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt)81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/kosmos/GeneralKosmosTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt276
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt150
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt53
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt)1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt83
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt205
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt134
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java664
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt897
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt388
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java162
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt149
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java313
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt279
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt208
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt309
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt123
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt11
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt85
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java6
-rw-r--r--packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml24
-rw-r--r--packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml24
-rw-r--r--packages/SystemUI/res/layout/controls_management.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml6
-rw-r--r--packages/SystemUI/res/layout/screen_record_options.xml4
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml22
-rw-r--r--packages/SystemUI/res/raw/trackpad_recent_apps_edu.json2
-rw-r--r--packages/SystemUI/res/raw/trackpad_recent_apps_success.json2
-rw-r--r--packages/SystemUI/res/values-af/strings.xml58
-rw-r--r--packages/SystemUI/res/values-am/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml46
-rw-r--r--packages/SystemUI/res/values-as/strings.xml38
-rw-r--r--packages/SystemUI/res/values-az/strings.xml38
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml38
-rw-r--r--packages/SystemUI/res/values-be/strings.xml38
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml38
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml38
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml40
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml40
-rw-r--r--packages/SystemUI/res/values-da/strings.xml38
-rw-r--r--packages/SystemUI/res/values-de/strings.xml38
-rw-r--r--packages/SystemUI/res/values-el/strings.xml40
-rw-r--r--packages/SystemUI/res/values-el/tiles_states_strings.xml8
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml38
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml30
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml38
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml38
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml40
-rw-r--r--packages/SystemUI/res/values-es/strings.xml38
-rw-r--r--packages/SystemUI/res/values-et/strings.xml38
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml44
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml40
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml38
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml38
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml40
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml42
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml38
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml38
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml38
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml40
-rw-r--r--packages/SystemUI/res/values-in/strings.xml38
-rw-r--r--packages/SystemUI/res/values-is/strings.xml38
-rw-r--r--packages/SystemUI/res/values-it/strings.xml46
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml46
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml40
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml38
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml44
-rw-r--r--packages/SystemUI/res/values-km/strings.xml38
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml38
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml38
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml40
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml40
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml40
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml38
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml38
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml40
-rw-r--r--packages/SystemUI/res/values-my/strings.xml40
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml42
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-or/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml38
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml44
-rw-r--r--packages/SystemUI/res/values-si/strings.xml38
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml40
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml38
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml38
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml38
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml40
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml40
-rw-r--r--packages/SystemUI/res/values-te/strings.xml38
-rw-r--r--packages/SystemUI/res/values-th/strings.xml38
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml38
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml40
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml38
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml38
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml38
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml40
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml40
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml42
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml38
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml38
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml16
-rw-r--r--packages/SystemUI/res/values/strings.xml50
-rw-r--r--packages/SystemUI/res/values/styles.xml5
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml4
-rw-r--r--packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json95
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java193
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt240
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt368
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt158
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt (renamed from packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt (renamed from packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt166
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt267
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperExclusions.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt395
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt193
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt128
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt149
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/IOnDoneCallback.aidl (renamed from packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/IScreenshotProxy.aidl (renamed from packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxy.kt (renamed from packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyClient.kt (renamed from packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyModule.kt (renamed from packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyService.kt (renamed from packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java4
-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/stack/AmbientState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java11
-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.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt)41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt334
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt2
-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/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt)27
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt471
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt410
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt)255
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java3
-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/GlanceableHubContainerControllerTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java411
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java11
-rw-r--r--packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt115
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt)3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt)3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt47
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt4
-rw-r--r--packages/Vcn/TEST_MAPPING8
-rw-r--r--packages/Vcn/flags/Android.bp38
-rw-r--r--packages/Vcn/flags/flags.aconfig (renamed from core/java/android/net/vcn/flags.aconfig)0
-rw-r--r--packages/Vcn/framework-b/Android.bp95
-rw-r--r--packages/Vcn/framework-b/api/current.txt122
-rw-r--r--packages/Vcn/framework-b/api/module-lib-current.txt27
-rw-r--r--packages/Vcn/framework-b/api/system-current.txt22
-rw-r--r--packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt2
-rw-r--r--packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java (renamed from core/java/android/net/ConnectivityFrameworkInitializerBaklava.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl (renamed from core/java/android/net/vcn/IVcnManagementService.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl (renamed from core/java/android/net/vcn/IVcnStatusCallback.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl (renamed from core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java (renamed from core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java)10
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl (renamed from core/java/android/net/vcn/VcnConfig.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java (renamed from core/java/android/net/vcn/VcnConfig.java)6
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java (renamed from core/java/android/net/vcn/VcnGatewayConnectionConfig.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java (renamed from core/java/android/net/vcn/VcnManager.java)8
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl (renamed from core/java/android/net/vcn/VcnNetworkPolicyResult.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java (renamed from core/java/android/net/vcn/VcnNetworkPolicyResult.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java (renamed from core/java/android/net/vcn/VcnTransportInfo.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl (renamed from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java (renamed from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java (renamed from core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java (renamed from core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java (renamed from core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java)6
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/CertUtils.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java (renamed from core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java)3
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java)0
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java (renamed from core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java (renamed from services/core/java/com/android/server/vcn/util/LogUtils.java)4
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java (renamed from services/core/java/com/android/server/vcn/util/MtuUtils.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java (renamed from services/core/java/com/android/server/vcn/util/OneWayBoolean.java)2
-rw-r--r--packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java (renamed from services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java)4
-rw-r--r--packages/Vcn/service-b/Android.bp54
-rw-r--r--packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java60
-rw-r--r--packages/Vcn/service-b/src/com/android/server/VcnManagementService.java (renamed from services/core/java/com/android/server/VcnManagementService.java)70
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java (renamed from services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java)4
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java (renamed from services/core/java/com/android/server/vcn/Vcn.java)4
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java (renamed from services/core/java/com/android/server/vcn/VcnContext.java)0
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java (renamed from services/core/java/com/android/server/vcn/VcnGatewayConnection.java)17
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java (renamed from services/core/java/com/android/server/vcn/VcnNetworkProvider.java)2
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java (renamed from services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java)5
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java (renamed from services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java)3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java (renamed from services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java)2
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java (renamed from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java)6
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java (renamed from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java)3
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java (renamed from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java)0
-rw-r--r--packages/VpnDialogs/AndroidManifest.xml1
-rw-r--r--proto/Android.bp4
-rw-r--r--ravenwood/Android.bp29
-rw-r--r--ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java54
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java14
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java15
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java16
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java60
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java49
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java2
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java8
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java139
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java46
-rw-r--r--ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java2
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java2
-rw-r--r--ravenwood/runtime-helper-src/framework/android/util/Log_host.java84
-rw-r--r--ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java204
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java2
-rw-r--r--ravenwood/runtime-jni/ravenwood_initializer.cpp191
-rw-r--r--ravenwood/runtime-jni/ravenwood_runtime.cpp6
-rw-r--r--ravenwood/runtime-jni/ravenwood_sysprop.cpp200
-rwxr-xr-xravenwood/scripts/extract-last-soong-commands.py89
-rw-r--r--ravenwood/test-authors.md39
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java42
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java108
-rw-r--r--ravenwood/texts/ravenwood-common-policies.txt6
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt9
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt19
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt7
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt4
-rw-r--r--services/Android.bp30
-rw-r--r--services/accessibility/accessibility.aconfig15
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java86
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java25
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyManager.java72
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java11
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java21
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java8
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java36
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java28
-rw-r--r--services/autofill/bugfixes.aconfig10
-rw-r--r--services/autofill/features.aconfig23
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java52
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java9
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java16
-rw-r--r--services/backup/flags.aconfig16
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java95
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java3
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java69
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java3
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java3
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java21
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java2
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java3
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java50
-rw-r--r--services/companion/java/com/android/server/companion/BackupRestoreProcessor.java7
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java10
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationDiskStore.java67
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java21
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java20
-rw-r--r--services/core/Android.bp8
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java27
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java170
-rw-r--r--services/core/java/com/android/server/DockObserver.java18
-rw-r--r--services/core/java/com/android/server/SecurityStateManagerService.java10
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java6
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java20
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java17
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java137
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java21
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java75
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java21
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java44
-rw-r--r--services/core/java/com/android/server/am/BroadcastFilter.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java11
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java73
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java101
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java10
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioServerPermissionProvider.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java157
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java4
-rw-r--r--services/core/java/com/android/server/audio/OWNERS1
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java36
-rw-r--r--services/core/java/com/android/server/biometrics/biometrics.aconfig7
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java6
-rw-r--r--services/core/java/com/android/server/content/SyncLogger.java4
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java151
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java73
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java39
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java132
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java51
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java5
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java1
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java52
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java23
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java78
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java155
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java (renamed from services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java)164
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java2
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java9
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig6
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginEventStorage.java134
-rw-r--r--services/core/java/com/android/server/display/plugin/PluginStorage.java10
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java55
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java16
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java61
-rw-r--r--services/core/java/com/android/server/incident/PendingReports.java16
-rw-r--r--services/core/java/com/android/server/input/InputDataStore.java344
-rw-r--r--services/core/java/com/android/server/input/InputFeatureFlagProvider.java101
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java44
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java6
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java59
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java81
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java11
-rw-r--r--services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java29
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java37
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java246
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java298
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java41
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java32
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java50
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java21
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java168
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java86
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java63
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudgerCache.java200
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java14
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java120
-rw-r--r--services/core/java/com/android/server/media/AudioManagerRouteController.java8
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java92
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java8
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java35
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java13
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java124
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java10
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java9
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java473
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java119
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java8
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OWNERS1
-rw-r--r--services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java118
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java7
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java11
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java214
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java141
-rw-r--r--services/core/java/com/android/server/pm/PackageAbiHelperImpl.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java74
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java90
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java103
-rw-r--r--services/core/java/com/android/server/pm/PackageVerificationState.java12
-rw-r--r--services/core/java/com/android/server/pm/SaferIntentUtils.java8
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java11
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING16
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java98
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java12
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java9
-rw-r--r--services/core/java/com/android/server/policy/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java22
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java201
-rw-r--r--services/core/java/com/android/server/power/hint/adpf_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java12
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java43
-rw-r--r--services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java16
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java4
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java12
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java23
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java57
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java71
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java33
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataSource.java5
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java42
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java87
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java14
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java177
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java21
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java50
-rw-r--r--services/core/java/com/android/server/telecom/TelecomLoaderService.java6
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java30
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java21
-rw-r--r--services/core/java/com/android/server/vcn/Android.bp13
-rw-r--r--services/core/java/com/android/server/vcn/TEST_MAPPING10
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java46
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java6
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java98
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java25
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java6
-rw-r--r--services/core/java/com/android/server/webkit/SystemInterface.java3
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java16
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java95
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java18
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java4
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java76
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java4
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java9
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java77
-rw-r--r--services/core/java/com/android/server/wm/CameraStateMonitor.java47
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java13
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java104
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java47
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCoordinator.java22
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java2
-rw-r--r--services/core/java/com/android/server/wm/DragState.java2
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java30
-rw-r--r--services/core/java/com/android/server/wm/InsetsControlTarget.java5
-rw-r--r--services/core/java/com/android/server/wm/OWNERS2
-rw-r--r--services/core/java/com/android/server/wm/PageSizeMismatchDialog.java72
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java4
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/SeamlessRotator.java2
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java43
-rw-r--r--services/core/java/com/android/server/wm/Task.java9
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java32
-rw-r--r--services/core/java/com/android/server/wm/Transition.java11
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerConstants.java26
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java119
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp53
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java17
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java179
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java19
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java8
-rw-r--r--services/java/com/android/server/SystemServer.java33
-rw-r--r--services/java/com/android/server/flags.aconfig8
-rw-r--r--services/manifest_services.xml5
-rw-r--r--services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java3
-rw-r--r--services/proguard.flags3
-rw-r--r--services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java46
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java15
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java97
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java7
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java43
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java4
-rw-r--r--services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java85
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java114
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt38
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java19
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java69
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java201
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java239
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java243
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java94
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java118
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java37
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java341
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java70
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java2
-rw-r--r--services/tests/ondeviceintelligencetests/Android.bp8
-rw-r--r--services/tests/ondeviceintelligencetests/OWNERS4
-rw-r--r--services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java11
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java25
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java30
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java4
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java205
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java11
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java12
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java11
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java6
-rw-r--r--services/tests/security/intrusiondetection/Android.bp5
-rw-r--r--services/tests/security/intrusiondetection/AndroidManifest.xml14
-rw-r--r--services/tests/security/intrusiondetection/AndroidTest.xml5
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java155
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp42
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml26
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java58
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java40
-rw-r--r--services/tests/servicestests/Android.bp10
-rw-r--r--services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java215
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java89
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java295
-rw-r--r--services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt155
-rw-r--r--services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java72
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java56
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java171
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java82
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java216
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java126
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java58
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java42
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java3
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java30
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java2
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java9
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl2
-rw-r--r--telephony/java/android/telephony/Annotation.java1
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java30
-rw-r--r--telephony/java/android/telephony/RadioAccessFamily.java9
-rw-r--r--telephony/java/android/telephony/ServiceState.java26
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java2
-rw-r--r--telephony/java/android/telephony/TelephonyDisplayInfo.java72
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java70
-rw-r--r--telephony/java/android/telephony/satellite/EarfcnRange.java5
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java11
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java16
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java8
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteInfo.java17
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java176
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java32
-rw-r--r--telephony/java/android/telephony/satellite/SatellitePosition.java9
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java3
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSessionStats.java229
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java64
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java58
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java18
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java7
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java10
-rw-r--r--telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java7
-rw-r--r--telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java38
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl6
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl13
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java3
-rw-r--r--tests/BinaryTransparencyHostTest/Android.bp2
-rw-r--r--tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java9
-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/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt41
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt48
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java306
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt4
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt325
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml21
-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/ActivityOptions.java15
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java71
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java31
-rw-r--r--tests/Input/src/com/android/server/input/InputDataStoreTests.kt504
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt4
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt107
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt759
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml38
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/ct_users.xml15
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java15
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java43
-rw-r--r--tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java30
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java230
-rw-r--r--tests/broadcasts/unit/Android.bp44
-rw-r--r--tests/broadcasts/unit/AndroidManifest.xml (renamed from services/tests/security/intrusiondetection/res/xml/device_admin.xml)13
-rw-r--r--tests/broadcasts/unit/AndroidTest.xml29
-rw-r--r--tests/broadcasts/unit/OWNERS2
-rw-r--r--tests/broadcasts/unit/TEST_MAPPING7
-rw-r--r--tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java231
-rw-r--r--tests/graphics/SilkFX/AndroidManifest.xml5
-rw-r--r--tests/graphics/SilkFX/res/layout/activity_background_blur.xml277
-rw-r--r--tests/graphics/SilkFX/res/layout/activity_glass.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/color_mode_controls.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/common_base.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/hdr_glows.xml3
-rw-r--r--tests/graphics/SilkFX/res/values/style.xml7
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt1
-rw-r--r--tests/vcn/Android.bp5
-rw-r--r--tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java (renamed from tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java)4
-rw-r--r--tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java (renamed from tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java)2
-rw-r--r--tests/vcn/java/com/android/server/VcnManagementServiceTest.java4
-rw-r--r--tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java5
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java26
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java2
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java2
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java2
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java2
-rw-r--r--tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java6
2732 files changed, 75343 insertions, 25355 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index e18470498f39..45e33ce4b6e9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -23,7 +23,7 @@ aconfig_declarations_group {
"aconfig_mediacodec_flags_java_lib",
"aconfig_settingslib_flags_java_lib",
"aconfig_trade_in_mode_flags_java_lib",
- "android-sdk-flags-java",
+ "adpf_flags_java_lib",
"android.adaptiveauth.flags-aconfig-java",
"android.app.appfunctions.flags-aconfig-java",
"android.app.assist.flags-aconfig-java",
@@ -56,13 +56,13 @@ aconfig_declarations_group {
"android.media.tv.flags-aconfig-java",
"android.multiuser.flags-aconfig-java",
"android.net.platform.flags-aconfig-java",
- "android.net.vcn.flags-aconfig-java-export",
"android.net.wifi.flags-aconfig-java",
"android.nfc.flags-aconfig-java",
"android.os.flags-aconfig-java",
"android.os.vibrator.flags-aconfig-java",
"android.permission.flags-aconfig-java",
"android.provider.flags-aconfig-java",
+ "android.sdk.flags-aconfig-java",
"android.security.flags-aconfig-java",
"android.server.app.flags-aconfig-java",
"android.service.autofill.flags-aconfig-java",
@@ -95,16 +95,19 @@ aconfig_declarations_group {
"com.android.internal.foldables.flags-aconfig-java",
"com.android.internal.os.flags-aconfig-java",
"com.android.internal.pm.pkg.component.flags-aconfig-java",
+ "com.android.internal.widget.flags-aconfig-java",
"com.android.media.flags.bettertogether-aconfig-java",
"com.android.media.flags.editing-aconfig-java",
"com.android.media.flags.performance-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
+ "com.android.net.http.flags-aconfig-exported-java",
"com.android.net.thread.platform.flags-aconfig-java",
"com.android.ranging.flags.ranging-aconfig-java-export",
"com.android.server.contextualsearch.flags-java",
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
+ "conscrypt_exported_aconfig_flags_lib",
"device_policy_aconfig_flags_lib",
"display_flags_lib",
"dropbox_flags_lib",
@@ -194,6 +197,14 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Conscrypt
+java_aconfig_library {
+ name: "conscrypt_exported_aconfig_flags_lib",
+ aconfig_declarations: "conscrypt-aconfig-flags",
+ mode: "exported",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Telecom
java_aconfig_library {
name: "telecom_flags_core_java_lib",
@@ -269,6 +280,19 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+aconfig_declarations {
+ name: "com.android.internal.widget.flags-aconfig",
+ package: "com.android.internal.widget.flags",
+ container: "system",
+ srcs: ["core/java/com/android/internal/widget/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.widget.flags-aconfig-java",
+ aconfig_declarations: "com.android.internal.widget.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Text
aconfig_declarations {
name: "com.android.text.flags-aconfig",
@@ -328,6 +352,7 @@ cc_aconfig_library {
name: "android_nfc_flags_aconfig_c_lib",
vendor_available: true,
aconfig_declarations: "android.nfc.flags-aconfig",
+ min_sdk_version: "34",
apex_available: [
"//apex_available:platform",
"com.android.nfcservices",
@@ -437,6 +462,8 @@ java_aconfig_library {
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.art",
+ "com.android.art.debug",
"com.android.btservices",
"com.android.mediaprovider",
"com.android.permission",
@@ -609,6 +636,11 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "aconfig_hardware_flags_c_lib",
+ aconfig_declarations: "android.hardware.flags-aconfig",
+}
+
// Widget
aconfig_declarations {
name: "android.widget.flags-aconfig",
@@ -652,6 +684,8 @@ java_aconfig_library {
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.art",
+ "com.android.art.debug",
"com.android.permission",
],
}
@@ -774,21 +808,6 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// OnDeviceIntelligence
-aconfig_declarations {
- name: "android.app.ondeviceintelligence-aconfig",
- exportable: true,
- package: "android.app.ondeviceintelligence.flags",
- container: "system",
- srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"],
-}
-
-java_aconfig_library {
- name: "android.app.ondeviceintelligence-aconfig-java",
- aconfig_declarations: "android.app.ondeviceintelligence-aconfig",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
// Permissions
aconfig_declarations {
name: "android.permission.flags-aconfig",
@@ -871,6 +890,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Adaptive Performance
+java_aconfig_library {
+ name: "adpf_flags_java_lib",
+ aconfig_declarations: "adpf_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Graphics
java_aconfig_library {
name: "hwui_flags_java_lib",
@@ -963,6 +989,18 @@ aconfig_declarations {
java_aconfig_library {
name: "android.app.flags-aconfig-java",
aconfig_declarations: "android.app.flags-aconfig",
+ min_sdk_version: "34",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+ name: "android.app.flags-aconfig-java-host",
+ aconfig_declarations: "android.app.flags-aconfig",
+ host_supported: true,
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -1174,25 +1212,6 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// VCN
-// TODO:376339506 Move the VCN code, the flag declaration and
-// java_aconfig_library to framework-connectivity-b
-aconfig_declarations {
- name: "android.net.vcn.flags-aconfig",
- package: "android.net.vcn",
- container: "com.android.tethering",
- exportable: true,
- srcs: ["core/java/android/net/vcn/*.aconfig"],
-}
-
-java_aconfig_library {
- name: "android.net.vcn.flags-aconfig-java-export",
- aconfig_declarations: "android.net.vcn.flags-aconfig",
- mode: "exported",
- min_sdk_version: "35",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
// DevicePolicy
aconfig_declarations {
name: "device_policy_aconfig_flags",
@@ -1210,6 +1229,17 @@ java_aconfig_library {
}
java_aconfig_library {
+ name: "device_policy_aconfig_flags_java_export",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
+}
+
+java_aconfig_library {
name: "device_policy_aconfig_flags_lib_host",
aconfig_declarations: "device_policy_aconfig_flags",
host_supported: true,
@@ -1437,6 +1467,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.appwidget.flags-aconfig-java-host",
+ aconfig_declarations: "android.appwidget.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// App
aconfig_declarations {
name: "android.server.app.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 424a4a71ce40..529da53e58f7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -83,11 +83,11 @@ filegroup {
":framework-telecomm-sources",
":framework-telephony-common-sources",
":framework-telephony-sources",
- ":framework-vcn-util-sources",
":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
+ ":vcn-utils-platform-sources",
":net-utils-framework-common-srcs",
// AIDL from frameworks/base/native/
@@ -313,9 +313,9 @@ java_defaults {
":framework-telecomm-sources",
":framework-telephony-common-sources",
":framework-telephony-sources",
- ":framework-vcn-util-sources",
":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
+ ":vcn-utils-platform-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
":net-utils-framework-common-srcs",
@@ -371,6 +371,7 @@ java_defaults {
"view-inspector-annotation-processor",
"staledataclass-annotation-processor",
"error_prone_android_framework",
+ "systemfeatures-metadata-processor",
],
// Exports needed for staledataclass-annotation-processor, see b/139342589.
javacflags: [
@@ -445,6 +446,9 @@ java_library {
default: [
"framework-platformcrashrecovery.impl",
],
+ }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+ true: [],
+ default: ["framework-ondeviceintelligence-platform.impl"],
}),
sdk_version: "core_platform",
installable: false,
@@ -488,6 +492,7 @@ java_library {
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base:__subpackages__",
+ "//packages/modules/NeuralNetworks:__subpackages__",
],
compile_dex: false,
headers_only: true,
@@ -583,6 +588,9 @@ java_library {
default: [
"framework-platformcrashrecovery-compat-config",
],
+ }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+ true: [],
+ default: ["framework-ondeviceintelligence-platform-compat-config"],
}),
}
@@ -597,7 +605,7 @@ filegroup {
srcs: [
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/WakeupMessage.java",
- "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
+ "packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java",
"telephony/java/android/telephony/Annotation.java",
],
}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index d748a3bebfef..d83109a1a986 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -18,7 +18,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/education
+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
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/android-sdk-flags/Android.bp b/android-sdk-flags/Android.bp
index 79a0b9a4f273..d1df2ca69f50 100644
--- a/android-sdk-flags/Android.bp
+++ b/android-sdk-flags/Android.bp
@@ -17,14 +17,21 @@ package {
}
aconfig_declarations {
- name: "android-sdk-flags",
+ name: "android.sdk.flags-aconfig",
package: "android.sdk",
container: "system",
srcs: ["flags.aconfig"],
}
java_aconfig_library {
- name: "android-sdk-flags-java",
- aconfig_declarations: "android-sdk-flags",
+ name: "android.sdk.flags-aconfig-java",
+ aconfig_declarations: "android.sdk.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+ name: "android.sdk.flags-aconfig-java-host",
+ aconfig_declarations: "android.sdk.flags-aconfig",
+ host_supported: true,
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 47a85498f51b..63624d8cad4a 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -29,6 +29,7 @@ flag {
namespace: "backstage_power"
description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
bug: "372529068"
+ is_exported: true
}
flag {
@@ -36,6 +37,7 @@ flag {
namespace: "backstage_power"
description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
bug: "374175032"
+ is_exported: true
}
flag {
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 810be8fc4220..fe95a59622f4 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -63,7 +63,7 @@ flag {
name: "remove_user_during_user_switch"
namespace: "backstage_power"
description: "Remove started user if user will be stopped due to user switch"
- bug: "321598070"
+ bug: "337077643"
}
flag {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a5a08fb9997c..fe80d1be9532 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1981,7 +1981,12 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getNumAppliedFlexibleConstraints(),
jobStatus.getNumDroppedFlexibleConstraints(),
jobStatus.getFilteredTraceTag(),
- jobStatus.getFilteredDebugTags());
+ jobStatus.getFilteredDebugTags(),
+ jobStatus.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ jobStatus.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
+
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2422,7 +2427,11 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getNumAppliedFlexibleConstraints(),
cancelled.getNumDroppedFlexibleConstraints(),
cancelled.getFilteredTraceTag(),
- cancelled.getFilteredDebugTags());
+ cancelled.getFilteredDebugTags(),
+ cancelled.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ cancelled.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 909a9b30ada4..2b401c8ff6b1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -546,7 +546,11 @@ public final class JobServiceContext implements ServiceConnection {
job.getNumAppliedFlexibleConstraints(),
job.getNumDroppedFlexibleConstraints(),
job.getFilteredTraceTag(),
- job.getFilteredDebugTags());
+ job.getFilteredDebugTags(),
+ job.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ job.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1681,7 +1685,11 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getNumAppliedFlexibleConstraints(),
completedJob.getNumDroppedFlexibleConstraints(),
completedJob.getFilteredTraceTag(),
- completedJob.getFilteredDebugTags());
+ completedJob.getFilteredDebugTags(),
+ completedJob.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ completedJob.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/api/Android.bp b/api/Android.bp
index 73262030ee37..14c2766d8887 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -105,6 +105,13 @@ combined_apis {
default: [
"framework-platformcrashrecovery",
],
+ }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+ true: [
+ "framework-ondeviceintelligence",
+ ],
+ default: [
+ "framework-ondeviceintelligence-platform",
+ ],
}) + select(release_flag("RELEASE_RANGING_STACK"), {
true: [
"framework-ranging",
@@ -119,7 +126,12 @@ combined_apis {
"service-permission",
"service-rkp",
"service-sdksandbox",
- ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ ] + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), {
+ true: [
+ "service-ondeviceintelligence",
+ ],
+ default: [],
+ }) + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
"true": [
"service-crashrecovery",
],
@@ -478,6 +490,7 @@ java_defaults {
"//frameworks/base/location",
"//frameworks/base/packages/CrashRecovery/framework",
"//frameworks/base/nfc",
+ "//packages/modules/NeuralNetworks:__subpackages__",
],
plugins: ["error_prone_android_framework"],
errorprone: {
diff --git a/api/api.go b/api/api.go
index 5ca24de1b46a..e4d783eba4c3 100644
--- a/api/api.go
+++ b/api/api.go
@@ -29,6 +29,7 @@ const i18n = "i18n.module.public.api"
const virtualization = "framework-virtualization"
const location = "framework-location"
const platformCrashrecovery = "framework-platformcrashrecovery"
+const ondeviceintelligence = "framework-ondeviceintelligence-platform"
var core_libraries_modules = []string{art, conscrypt, i18n}
@@ -40,7 +41,7 @@ var core_libraries_modules = []string{art, conscrypt, i18n}
// APIs.
// In addition, the modules in this list are allowed to contribute to test APIs
// stubs.
-var non_updatable_modules = []string{virtualization, location, platformCrashrecovery}
+var non_updatable_modules = []string{virtualization, location, platformCrashrecovery, ondeviceintelligence}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
diff --git a/boot/Android.bp b/boot/Android.bp
index 6eead42a4d30..eaa984ac0cdd 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -31,6 +31,7 @@ soong_config_module_type {
"car_bootclasspath_fragment",
"nfc_apex_bootclasspath_fragment",
"release_crashrecovery_module",
+ "release_ondevice_intelligence_module",
"release_package_profiling_module",
],
properties: [
@@ -176,6 +177,15 @@ custom_platform_bootclasspath {
},
],
},
+ release_ondevice_intelligence_module: {
+ fragments: [
+ // only used when ondeviceintelligence is moved to neuralnetworks module
+ {
+ apex: "com.android.neuralnetworks",
+ module: "com.android.ondeviceintelligence-bootclasspath-fragment",
+ },
+ ],
+ },
release_package_profiling_module: {
fragments: [
// only used if profiling is enabled.
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 58763a7f9aca..d9ff19051de9 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -165,6 +165,7 @@ cc_test {
],
host_supported: true,
test_suites: ["general-tests"],
+ require_root: true,
srcs: [
"tests/BinaryStreamVisitorTests.cpp",
"tests/CommandLineOptionsTests.cpp",
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 57ae3548123b..f22a481c1f28 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -22,6 +22,7 @@
#include <utility>
#include <vector>
+#include "android-base/scopeguard.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/AssetManager.h"
#include "androidfw/Util.h"
@@ -269,27 +270,40 @@ struct ResState {
std::unique_ptr<AssetManager2> am;
ZipAssetsProvider* zip_assets;
- static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+ static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider>&& zip,
package_property_t flags) {
ResState state;
state.zip_assets = zip.get();
if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
- return Error("failed to load apk asset");
+ return Error("failed to load apk asset for '%s'",
+ state.zip_assets->GetDebugName().c_str());
}
+ // Make sure we put ZipAssetsProvider where we took it if initialization fails, so the
+ // original object stays valid for any next call it may get.
+ auto scoped_restore_zip_assets = android::base::ScopeGuard([&zip, &state]() {
+ zip = std::unique_ptr<ZipAssetsProvider>(
+ static_cast<ZipAssetsProvider*>(
+ std::move(const_cast<ApkAssets&>(*state.apk_assets)).TakeAssetsProvider().release()));
+ });
+
if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
- return Error("failed to retrieve loaded arsc");
+ return Error("failed to retrieve loaded arsc for '%s'",
+ state.zip_assets->GetDebugName().c_str());
}
if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
- return Error("failed to retrieve loaded package at index 0");
+ return Error("failed to retrieve loaded package at index 0 for '%s'",
+ state.zip_assets->GetDebugName().c_str());
}
state.am = std::make_unique<AssetManager2>();
if (!state.am->SetApkAssets({state.apk_assets}, false)) {
- return Error("failed to create asset manager");
+ return Error("failed to create asset manager for '%s'",
+ state.zip_assets->GetDebugName().c_str());
}
+ scoped_restore_zip_assets.Disable();
return state;
}
};
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 1b656e8c2088..7093614f4047 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -214,6 +214,20 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) {
ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), TestConstants::OVERLAY_NAME_ALL_POLICIES);
}
+TEST(IdmapTests, TargetContainerWorksAfterError) {
+ auto target = TargetResourceContainer::FromPath(GetTestDataPath() + "/target/target-bad.apk");
+ ASSERT_TRUE(target);
+
+ auto crc = target->get()->GetCrc();
+ ASSERT_TRUE(crc);
+
+ // This call tries to construct the full ApkAssets state, and fails.
+ ASSERT_FALSE(target->get()->DefinesOverlayable());
+ auto crc2 = target->get()->GetCrc();
+ ASSERT_TRUE(crc2);
+ EXPECT_EQ(*crc, *crc2);
+}
+
TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
diff --git a/cmds/idmap2/tests/data/target/target-bad.apk b/cmds/idmap2/tests/data/target/target-bad.apk
new file mode 100644
index 000000000000..fd8678238c4d
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/target-bad.apk
Binary files differ
diff --git a/core/api/current.txt b/core/api/current.txt
index af7d6f1a5da1..6367002a6693 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -140,6 +140,7 @@ package android {
field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL";
field public static final String MANAGE_DEVICE_POLICY_AIRPLANE_MODE = "android.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE";
field public static final String MANAGE_DEVICE_POLICY_APPS_CONTROL = "android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL";
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String MANAGE_DEVICE_POLICY_APP_FUNCTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS";
field public static final String MANAGE_DEVICE_POLICY_APP_RESTRICTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS";
field public static final String MANAGE_DEVICE_POLICY_APP_USER_DATA = "android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA";
field public static final String MANAGE_DEVICE_POLICY_ASSIST_CONTENT = "android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT";
@@ -247,6 +248,7 @@ package android {
field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+ field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String READ_COLOR_ZONES = "android.permission.READ_COLOR_ZONES";
field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
field @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") public static final String READ_DROPBOX_DATA = "android.permission.READ_DROPBOX_DATA";
field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
@@ -1203,6 +1205,7 @@ package android {
field public static final int minResizeHeight = 16843670; // 0x1010396
field public static final int minResizeWidth = 16843669; // 0x1010395
field public static final int minSdkVersion = 16843276; // 0x101020c
+ field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull;
field public static final int minWidth = 16843071; // 0x101013f
field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
field public static final int minimumVerticalAngle = 16843902; // 0x101047e
@@ -2186,6 +2189,23 @@ package android {
public static final class R.dimen {
ctor public R.dimen();
field public static final int app_icon_size = 17104896; // 0x1050000
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusLarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusMedium;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusSmall;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXlarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXsmall;
field public static final int dialog_min_width_major = 17104899; // 0x1050003
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -2482,6 +2502,18 @@ package android {
ctor public R.integer();
field public static final int config_longAnimTime = 17694722; // 0x10e0002
field public static final int config_mediumAnimTime = 17694721; // 0x10e0001
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectStiffness;
+ field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialStiffness;
field public static final int config_shortAnimTime = 17694720; // 0x10e0000
field @Deprecated public static final int status_bar_notification_info_maxnum = 17694723; // 0x10e0003
}
@@ -6450,6 +6482,7 @@ package android.app {
method public String getSortKey();
method public long getTimeoutAfter();
method public boolean hasImage();
+ method @FlaggedApi("android.app.api_rich_ongoing") public boolean hasPromotableCharacteristics();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final int BADGE_ICON_LARGE = 2; // 0x2
@@ -8048,6 +8081,7 @@ package android.app.admin {
field public static final String ACCOUNT_MANAGEMENT_DISABLED_POLICY = "accountManagementDisabled";
field public static final String APPLICATION_HIDDEN_POLICY = "applicationHidden";
field public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions";
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String APP_FUNCTIONS_POLICY = "appFunctions";
field public static final String AUTO_TIMEZONE_POLICY = "autoTimezone";
field public static final String AUTO_TIME_POLICY = "autoTime";
field public static final String BACKUP_SERVICE_POLICY = "backupService";
@@ -8097,6 +8131,7 @@ package android.app.admin {
method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName);
method @Nullable public java.util.Set<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName);
method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
+ method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS, conditional=true) public int getAppFunctionsPolicy();
method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@Nullable android.content.ComponentName);
@@ -8255,6 +8290,7 @@ package android.app.admin {
method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.Set<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS, conditional=true) public void setAppFunctionsPolicy(int);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE, conditional=true) public boolean setApplicationHidden(@Nullable android.content.ComponentName, String, boolean);
method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8381,6 +8417,9 @@ package android.app.admin {
field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final int APP_FUNCTIONS_DISABLED = 1; // 0x1
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final int APP_FUNCTIONS_DISABLED_CROSS_PROFILE = 2; // 0x2
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final int APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_DISABLED = 1; // 0x1
field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_ENABLED = 2; // 0x2
field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
@@ -8845,6 +8884,7 @@ package android.app.appfunctions {
field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
field public static final int ERROR_DENIED = 1000; // 0x3e8
field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2
field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
@@ -8916,6 +8956,7 @@ package android.app.assist {
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR;
field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA";
+ field @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public static final String EXTRA_SESSION_TRANSFER_WEB_URI = "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
}
public class AssistStructure implements android.os.Parcelable {
@@ -9998,12 +10039,12 @@ package android.companion {
method public int describeContents();
method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
+ method @FlaggedApi("android.companion.association_tag") @Nullable public android.companion.DeviceId getDeviceId();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
method public int getId();
method public int getSystemDataSyncFlags();
- method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
method public boolean isSelfManaged();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
@@ -10076,7 +10117,6 @@ package android.companion {
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
- method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
@@ -10088,11 +10128,11 @@ package android.companion {
method @FlaggedApi("android.companion.perm_sync_user_consent") public boolean isPermissionTransferUserConsented(int);
method @FlaggedApi("android.companion.unpair_associated_device") @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond(int);
method public void requestNotificationAccess(android.content.ComponentName);
- method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @FlaggedApi("android.companion.association_tag") public void setDeviceId(int, @Nullable android.companion.DeviceId);
+ method @Deprecated @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
- method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @Deprecated @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -10120,9 +10160,9 @@ package android.companion {
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
- method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+ method @Deprecated @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
- method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
+ method @Deprecated @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
method @FlaggedApi("android.companion.device_presence") @MainThread public void onDevicePresenceEvent(@NonNull android.companion.DevicePresenceEvent);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -10133,6 +10173,21 @@ package android.companion {
public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
}
+ @FlaggedApi("android.companion.association_tag") public final class DeviceId implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getCustomId();
+ method @Nullable public android.net.MacAddress getMacAddress();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.DeviceId> CREATOR;
+ }
+
+ public static final class DeviceId.Builder {
+ ctor public DeviceId.Builder();
+ method @NonNull public android.companion.DeviceId build();
+ method @NonNull public android.companion.DeviceId.Builder setCustomId(@Nullable String);
+ method @NonNull public android.companion.DeviceId.Builder setMacAddress(@Nullable android.net.MacAddress);
+ }
+
public class DeviceNotAssociatedException extends java.lang.RuntimeException {
}
@@ -17042,7 +17097,7 @@ package android.graphics {
method public void arcTo(@NonNull android.graphics.RectF, float, float);
method public void arcTo(float, float, float, float, float, float, boolean);
method public void close();
- method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
+ method public void computeBounds(@NonNull android.graphics.RectF, boolean);
method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
method public void conicTo(float, float, float, float, float);
method public void cubicTo(float, float, float, float, float, float);
@@ -20767,7 +20822,9 @@ package android.hardware.display {
public final class VirtualDisplayConfig implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
method public int getDensityDpi();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDimBrightness();
method @NonNull public java.util.Set<java.lang.String> getDisplayCategories();
method public int getFlags();
method public int getHeight();
@@ -20779,10 +20836,17 @@ package android.hardware.display {
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR;
}
+ @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public static interface VirtualDisplayConfig.BrightnessListener {
+ method public void onBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
public static final class VirtualDisplayConfig.Builder {
ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String);
method @NonNull public android.hardware.display.VirtualDisplayConfig build();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setBrightnessListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.display.VirtualDisplayConfig.BrightnessListener);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDimBrightness(@FloatRange(from=0.0f, to=1.0f) float);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
@@ -21731,6 +21795,18 @@ package android.media {
field public static final int ENCODING_DTS_UHD_P2 = 30; // 0x1e
field public static final int ENCODING_E_AC3 = 6; // 0x6
field public static final int ENCODING_E_AC3_JOC = 18; // 0x12
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC = 42; // 0x2a
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC = 43; // 0x2b
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS = 41; // 0x29
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM = 44; // 0x2c
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_PROFILE_AAC = 38; // 0x26
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_PROFILE_FLAC = 39; // 0x27
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_PROFILE_OPUS = 37; // 0x25
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_BASE_PROFILE_PCM = 40; // 0x28
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_SIMPLE_PROFILE_AAC = 34; // 0x22
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_SIMPLE_PROFILE_FLAC = 35; // 0x23
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_SIMPLE_PROFILE_OPUS = 33; // 0x21
+ field @FlaggedApi("android.media.audio.iamf_definitions_api") public static final int ENCODING_IAMF_SIMPLE_PROFILE_PCM = 36; // 0x24
field public static final int ENCODING_IEC61937 = 13; // 0xd
field public static final int ENCODING_INVALID = 0; // 0x0
field public static final int ENCODING_MP3 = 9; // 0x9
@@ -24852,7 +24928,9 @@ package android.media {
method @Nullable public android.net.Uri getIconUri();
method @NonNull public String getId();
method @NonNull public CharSequence getName();
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.List<java.util.Set<java.lang.String>> getRequiredPermissions();
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
method public int getType();
method public int getVolume();
method public int getVolumeHandling();
@@ -24868,6 +24946,8 @@ package android.media {
field public static final String FEATURE_REMOTE_AUDIO_PLAYBACK = "android.media.route.feature.REMOTE_AUDIO_PLAYBACK";
field public static final String FEATURE_REMOTE_PLAYBACK = "android.media.route.feature.REMOTE_PLAYBACK";
field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
+ field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_REMOTE = 4; // 0x4
+ field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1; // 0x1
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
@@ -24917,7 +24997,10 @@ package android.media {
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.Set<java.lang.String>);
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.List<java.util.Set<java.lang.String>>);
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
+ method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -27087,6 +27170,15 @@ package android.media.projection {
package android.media.quality {
+ @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class ActiveProcessingPicture implements android.os.Parcelable {
+ ctor public ActiveProcessingPicture(int, @NonNull String);
+ method public int describeContents();
+ method public int getId();
+ method @NonNull public String getProfileId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.ActiveProcessingPicture> CREATOR;
+ }
+
@FlaggedApi("android.media.tv.flags.media_quality_fw") public final class AmbientBacklightEvent implements android.os.Parcelable {
ctor public AmbientBacklightEvent(int, @Nullable android.media.quality.AmbientBacklightMetadata);
method public int describeContents();
@@ -27105,10 +27197,10 @@ package android.media.quality {
method public int describeContents();
method public int getColorFormat();
method public int getCompressAlgorithm();
- method @IntRange(from=0) public int getHorizontalZonesNumber();
+ method @IntRange(from=0, to=128) public int getHorizontalZonesNumber();
method @NonNull public String getPackageName();
method public int getSource();
- method @IntRange(from=0) public int getVerticalZonesNumber();
+ method @IntRange(from=0, to=80) public int getVerticalZonesNumber();
method @NonNull public int[] getZonesColors();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.AmbientBacklightMetadata> CREATOR;
@@ -27138,8 +27230,30 @@ package android.media.quality {
}
public static final class MediaQualityContract.PictureQuality {
+ field public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED = "auto_picture_quality_enabled";
+ field public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled";
+ field public static final String PARAMETER_BLUE_STRETCH = "blue_stretch";
field public static final String PARAMETER_BRIGHTNESS = "brightness";
+ field public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature";
+ field public static final String PARAMETER_COLOR_TUNE = "color_tune";
+ field public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain";
+ field public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset";
+ field public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness";
+ field public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain";
+ field public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset";
+ field public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue";
+ field public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain";
+ field public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset";
+ field public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation";
field public static final String PARAMETER_CONTRAST = "contrast";
+ field public static final String PARAMETER_DECONTOUR = "decontour";
+ field public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control";
+ field public static final String PARAMETER_FILM_MODE = "film_mode";
+ field public static final String PARAMETER_FLESH_TONE = "flesh_tone";
+ field public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming";
+ field public static final String PARAMETER_HUE = "hue";
+ field public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction";
+ field public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction";
field public static final String PARAMETER_SATURATION = "saturation";
field public static final String PARAMETER_SHARPNESS = "sharpness";
}
@@ -27151,13 +27265,14 @@ package android.media.quality {
}
@FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager {
+ method public void addActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
method public void createPictureProfile(@NonNull android.media.quality.PictureProfile);
method public void createSoundProfile(@NonNull android.media.quality.SoundProfile);
- method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles();
- method @NonNull public java.util.List<android.media.quality.SoundProfile> getAvailableSoundProfiles();
+ method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles(boolean);
+ method @NonNull public java.util.List<android.media.quality.SoundProfile> getAvailableSoundProfiles(boolean);
method @NonNull public java.util.List<android.media.quality.ParamCapability> getParamCapabilities(@NonNull java.util.List<java.lang.String>);
- method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String);
- method @Nullable public android.media.quality.SoundProfile getSoundProfile(int, @NonNull String);
+ method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String, boolean);
+ method @Nullable public android.media.quality.SoundProfile getSoundProfile(int, @NonNull String, boolean);
method public boolean isAmbientBacklightEnabled();
method public boolean isAutoPictureQualityEnabled();
method public boolean isAutoSoundQualityEnabled();
@@ -27165,6 +27280,7 @@ package android.media.quality {
method public void registerAmbientBacklightCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.AmbientBacklightCallback);
method public void registerPictureProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.PictureProfileCallback);
method public void registerSoundProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.SoundProfileCallback);
+ method public void removeActiveProcessingPictureListener(@NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
method public void removePictureProfile(@NonNull String);
method public void removeSoundProfile(@NonNull String);
method public void setAmbientBacklightEnabled(boolean);
@@ -27176,6 +27292,10 @@ package android.media.quality {
method public void updateSoundProfile(@NonNull String, @NonNull android.media.quality.SoundProfile);
}
+ public static interface MediaQualityManager.ActiveProcessingPictureListener {
+ method public void onActiveProcessingPicturesChanged(@NonNull java.util.List<android.media.quality.ActiveProcessingPicture>);
+ }
+
public abstract static class MediaQualityManager.AmbientBacklightCallback {
ctor public MediaQualityManager.AmbientBacklightCallback();
method public void onAmbientBacklightEvent(@NonNull android.media.quality.AmbientBacklightEvent);
@@ -27183,7 +27303,7 @@ package android.media.quality {
public abstract static class MediaQualityManager.PictureProfileCallback {
ctor public MediaQualityManager.PictureProfileCallback();
- method public void onError(int);
+ method public void onError(@Nullable String, int);
method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List<android.media.quality.ParamCapability>);
method public void onPictureProfileAdded(@NonNull String, @NonNull android.media.quality.PictureProfile);
method public void onPictureProfileRemoved(@NonNull String, @NonNull android.media.quality.PictureProfile);
@@ -27192,7 +27312,7 @@ package android.media.quality {
public abstract static class MediaQualityManager.SoundProfileCallback {
ctor public MediaQualityManager.SoundProfileCallback();
- method public void onError(int);
+ method public void onError(@Nullable String, int);
method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List<android.media.quality.ParamCapability>);
method public void onSoundProfileAdded(@NonNull String, @NonNull android.media.quality.SoundProfile);
method public void onSoundProfileRemoved(@NonNull String, @NonNull android.media.quality.SoundProfile);
@@ -29836,6 +29956,7 @@ package android.net.http {
public class X509TrustManagerExtensions {
ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], String, String) throws java.security.cert.CertificateException;
+ method @FlaggedApi("android.net.platform.flags.x509_extensions_certificate_transparency") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException;
method public boolean isSameTrustConfiguration(String, String);
method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
}
@@ -30078,128 +30199,6 @@ package android.net.sip {
}
-package android.net.vcn {
-
- public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
- method public int getCbs();
- method public int getDun();
- method public int getIms();
- method public int getInternet();
- method public int getMms();
- method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
- method public int getOpportunistic();
- method public int getRcs();
- method public int getRoaming();
- method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
- }
-
- public static final class VcnCellUnderlyingNetworkTemplate.Builder {
- ctor public VcnCellUnderlyingNetworkTemplate.Builder();
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
- method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
- }
-
- public final class VcnConfig implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
- method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
- }
-
- public static final class VcnConfig.Builder {
- ctor public VcnConfig.Builder(@NonNull android.content.Context);
- method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
- method @NonNull public android.net.vcn.VcnConfig build();
- method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
- }
-
- public final class VcnGatewayConnectionConfig {
- method @NonNull public int[] getExposedCapabilities();
- method @NonNull public String getGatewayConnectionName();
- method @IntRange(from=0x500) public int getMaxMtu();
- method public int getMinUdpPort4500NatTimeoutSeconds();
- method @NonNull public long[] getRetryIntervalsMillis();
- method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
- method public boolean hasGatewayOption(int);
- method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
- field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
- field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
- }
-
- public static final class VcnGatewayConnectionConfig.Builder {
- ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
- method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
- }
-
- public class VcnManager {
- method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
- method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
- method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
- method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
- method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
- field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
- field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
- field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
- field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
- field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
- field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
- field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
- }
-
- public abstract static class VcnManager.VcnStatusCallback {
- ctor public VcnManager.VcnStatusCallback();
- method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
- method public abstract void onStatusChanged(int);
- }
-
- public abstract class VcnUnderlyingNetworkTemplate {
- method public int getMetered();
- method public int getMinEntryDownstreamBandwidthKbps();
- method public int getMinEntryUpstreamBandwidthKbps();
- method public int getMinExitDownstreamBandwidthKbps();
- method public int getMinExitUpstreamBandwidthKbps();
- field public static final int MATCH_ANY = 0; // 0x0
- field public static final int MATCH_FORBIDDEN = 2; // 0x2
- field public static final int MATCH_REQUIRED = 1; // 0x1
- }
-
- public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
- method @NonNull public java.util.Set<java.lang.String> getSsids();
- }
-
- public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
- ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
- method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
- method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
- method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
- method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
- method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
- }
-
-}
-
package android.opengl {
public class EGL14 {
@@ -33612,14 +33611,12 @@ package android.os {
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
ctor public CpuHeadroomParams();
method public int getCalculationType();
- method @IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public long getCalculationWindowMillis();
+ method @IntRange(from=0x32, to=0x2710) public long getCalculationWindowMillis();
method public void setCalculationType(int);
- method public void setCalculationWindowMillis(@IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
+ method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int);
method public void setTids(@NonNull int...);
field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
- field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
- field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public final class CpuUsageInfo implements android.os.Parcelable {
@@ -33872,13 +33869,11 @@ package android.os {
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
ctor public GpuHeadroomParams();
method public int getCalculationType();
- method @IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public int getCalculationWindowMillis();
+ method @IntRange(from=0x32, to=0x2710) public int getCalculationWindowMillis();
method public void setCalculationType(int);
- method public void setCalculationWindowMillis(@IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
+ method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int);
field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
- field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
- field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public class Handler {
@@ -34770,7 +34765,10 @@ package android.os {
method public android.os.MessageQueue getMessageQueue();
method public boolean hasMessages(android.os.Handler, Object, int);
method public boolean hasMessages(android.os.Handler, Object, Runnable);
+ method @FlaggedApi("android.os.message_queue_testability") public boolean isBlockedOnSyncBarrier();
method public android.os.Message next();
+ method @FlaggedApi("android.os.message_queue_testability") @Nullable public Long peekWhen();
+ method @FlaggedApi("android.os.message_queue_testability") @Nullable public android.os.Message pop();
method public void recycle(android.os.Message);
method public void release();
}
@@ -40823,8 +40821,10 @@ package android.security.keystore {
method @NonNull public java.util.List<java.security.cert.X509Certificate> getGrantedCertificateChainFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
method @NonNull public java.security.Key getGrantedKeyFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
method @NonNull public java.security.KeyPair getGrantedKeyPairFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
+ method @FlaggedApi("android.security.keystore2.attest_modules") @NonNull public byte[] getSupplementaryAttestationInfo(int) throws android.security.KeyStoreException;
method public long grantKeyAccess(@NonNull String, int) throws android.security.KeyStoreException, java.security.UnrecoverableKeyException;
method public void revokeKeyAccess(@NonNull String, int) throws android.security.KeyStoreException, java.security.UnrecoverableKeyException;
+ field public static final int MODULE_HASH = -1879047468; // 0x900002d4
}
public class SecureKeyImportUnavailableException extends java.security.ProviderException {
@@ -40880,13 +40880,14 @@ package android.service.autofill {
public abstract class AutofillService extends android.app.Service {
ctor public AutofillService();
- method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+ method @Deprecated @FlaggedApi("android.service.autofill.autofill_session_destroyed") @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+ method @FlaggedApi("android.service.autofill.autofill_session_destroyed") public void onSessionDestroyed(@Nullable android.service.autofill.FillEventHistory);
field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
@@ -41047,7 +41048,7 @@ package android.service.autofill {
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6
- field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDENTIAL_MANAGER = 4; // 0x4
field public static final int UI_TYPE_DIALOG = 3; // 0x3
field public static final int UI_TYPE_INLINE = 2; // 0x2
field public static final int UI_TYPE_MENU = 1; // 0x1
@@ -42616,9 +42617,10 @@ package android.service.settings.preferences {
method public boolean isWritable();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR;
- field public static final int INTENT_ONLY = 2; // 0x2
- field public static final int NOT_SENSITIVE = 0; // 0x0
- field public static final int SENSITIVE = 1; // 0x1
+ field public static final int EXPECT_POST_CONFIRMATION = 1; // 0x1
+ field public static final int EXPECT_PRE_CONFIRMATION = 2; // 0x2
+ field public static final int NO_DIRECT_ACCESS = 3; // 0x3
+ field public static final int NO_SENSITIVITY = 0; // 0x0
}
public static final class SettingsPreferenceMetadata.Builder {
@@ -45038,6 +45040,7 @@ package android.telephony {
field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = "regional_satellite_earfcn_bundle";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool";
field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
@@ -45051,6 +45054,7 @@ package android.telephony {
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
@@ -45062,6 +45066,8 @@ package android.telephony {
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_screen_off_inactivity_timeout_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = "satellite_roaming_turn_off_session_for_emergency_call_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT = "satellite_sos_max_datagram_size_bytes_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY = "satellite_supported_msg_apps_string_array";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -47457,6 +47463,7 @@ package android.telephony {
field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final long NETWORK_TYPE_BITMASK_NB_IOT_NTN = 1048576L; // 0x100000L
field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
@@ -47476,6 +47483,7 @@ package android.telephony {
field @Deprecated public static final int NETWORK_TYPE_IDEN = 11; // 0xb
field public static final int NETWORK_TYPE_IWLAN = 18; // 0x12
field public static final int NETWORK_TYPE_LTE = 13; // 0xd
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int NETWORK_TYPE_NB_IOT_NTN = 21; // 0x15
field public static final int NETWORK_TYPE_NR = 20; // 0x14
field public static final int NETWORK_TYPE_TD_SCDMA = 17; // 0x11
field public static final int NETWORK_TYPE_UMTS = 3; // 0x3
@@ -53465,6 +53473,7 @@ package android.view {
field @NonNull public static final android.os.Parcelable.Creator<android.view.Surface> CREATOR;
field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
+ field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; // 0x2
field public static final int ROTATION_0 = 0; // 0x0
field public static final int ROTATION_180 = 2; // 0x2
field public static final int ROTATION_270 = 3; // 0x3
@@ -55243,8 +55252,8 @@ package android.view {
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
method public abstract void setWebDomain(@Nullable String);
- field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
- field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.extra.VIRTUAL_STRUCTURE_TYPE";
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
}
public abstract static class ViewStructure.HtmlInfo {
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index ad5bd31828e0..e71dffaf152d 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,10 +1,4 @@
// Baseline format: 1.0
-ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE:
- Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
-ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
- Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
-
-
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
@@ -1191,10 +1185,6 @@ UnflaggedApi: android.R.dimen#system_corner_radius_xlarge:
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge
UnflaggedApi: android.R.dimen#system_corner_radius_xsmall:
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall
-UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum:
- Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum
-UnflaggedApi: android.R.string#status_bar_notification_info_overflow:
- Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1a949d84c052..bca2ce2473c5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -100,6 +100,7 @@ package android.content {
method @NonNull public android.content.Context createContextForSdkInSandbox(@NonNull android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public android.os.IBinder getProcessToken();
method @NonNull public android.os.UserHandle getUser();
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public static final int BIND_FOREGROUND_SERVICE = 67108864; // 0x4000000
field public static final String PAC_PROXY_SERVICE = "pac_proxy";
field public static final String TEST_NETWORK_SERVICE = "test_network";
field @FlaggedApi("android.os.mainline_vcn_platform_api") public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
@@ -129,6 +130,7 @@ package android.content.pm {
public abstract class PackageManager {
method @NonNull public String getSdkSandboxPackageName();
+ method @FlaggedApi("android.content.pm.cloud_compilation_pm") @NonNull public static android.content.pm.SigningInfo getVerifiedSigningInfo(@NonNull String, int) throws android.content.pm.SigningInfoException;
method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int);
field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
@@ -139,6 +141,18 @@ package android.content.pm {
method @NonNull public String getPackageName();
}
+ public final class SigningInfo implements android.os.Parcelable {
+ method @FlaggedApi("android.content.pm.cloud_compilation_pm") public boolean signersMatchExactly(@NonNull android.content.pm.SigningInfo);
+ field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_JAR = 1; // 0x1
+ field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V2 = 2; // 0x2
+ field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V3 = 3; // 0x3
+ field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V4 = 4; // 0x4
+ }
+
+ @FlaggedApi("android.content.pm.cloud_compilation_pm") public class SigningInfoException extends java.lang.Exception {
+ method @FlaggedApi("android.content.pm.cloud_compilation_pm") public int getCode();
+ }
+
}
package android.hardware.usb {
@@ -251,10 +265,6 @@ package android.media.session {
package android.net {
- @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
- }
-
public class LocalSocket implements java.io.Closeable {
ctor public LocalSocket(@NonNull java.io.FileDescriptor);
}
@@ -314,25 +324,6 @@ package android.net.netstats {
}
-package android.net.vcn {
-
- @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
- field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
- }
-
- @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
- ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
- method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
- }
-
-}
-
package android.net.wifi {
public final class WifiMigration {
@@ -491,6 +482,10 @@ package android.os {
field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
}
+ public class UpdateEngine {
+ method @FlaggedApi("android.os.update_engine_api") public void triggerPostinstall(@NonNull String);
+ }
+
}
package android.os.storage {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 83699ac30939..4a4776dc590e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -78,6 +78,7 @@ package android {
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
+ field @FlaggedApi("android.location.flags.population_density_provider") public static final String BIND_POPULATION_DENSITY_PROVIDER_SERVICE = "android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE";
field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
@@ -327,6 +328,7 @@ package android {
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+ field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final String READ_SUBSCRIPTION_PLANS = "android.permission.READ_SUBSCRIPTION_PLANS";
field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
@@ -516,6 +518,7 @@ package android {
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_defaultNotes = 17039429; // 0x1040045
+ field @FlaggedApi("android.permission.flags.cross_user_role_platform_api_enabled") public static final int config_defaultReservedForTestingProfileGroupExclusivity;
field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo = 17039432; // 0x1040048
field public static final int config_defaultSms = 17039396; // 0x1040024
field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet = 17039433; // 0x1040049
@@ -548,6 +551,7 @@ package android {
field public static final int config_systemTextIntelligence = 17039414; // 0x1040036
field public static final int config_systemUi = 17039418; // 0x104003a
field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
+ field @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence;
field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
field public static final int config_systemWearHealthService = 17039428; // 0x1040044
field public static final int config_systemWellbeing = 17039408; // 0x1040030
@@ -902,7 +906,7 @@ package android.app {
public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getAttributionTag();
- method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @Nullable public String getDeviceId();
+ method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @NonNull public String getDeviceId();
method @Nullable public String getPackageName();
method @IntRange(from=0) public int getUid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -1289,6 +1293,7 @@ package android.app {
method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
method public void setDisplayOffset(android.os.IBinder, int, int);
method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray<android.graphics.Rect>, boolean, int) throws java.io.IOException;
+ method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithDescription(@NonNull java.io.InputStream, @NonNull android.app.wallpaper.WallpaperDescription, boolean, int) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
@@ -2272,149 +2277,6 @@ package android.app.job {
}
-package android.app.ondeviceintelligence {
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
- method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
- method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
- method public default void onDownloadProgress(long);
- method public default void onDownloadStarted(long);
- field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3
- field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2
- field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1
- field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4
- field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.os.PersistableBundle getFeatureParams();
- method public int getId();
- method @Nullable public String getModelName();
- method @Nullable public String getName();
- method public int getType();
- method public int getVariant();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR;
- }
-
- public static final class Feature.Builder {
- ctor public Feature.Builder(int);
- method @NonNull public android.app.ondeviceintelligence.Feature build();
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int);
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
- ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
- ctor public FeatureDetails(int);
- method public int describeContents();
- method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method public int getFeatureStatus();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
- field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
- field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1
- field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2
- field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4
- field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public final class InferenceInfo implements android.os.Parcelable {
- method public int describeContents();
- method public long getEndTimeMillis();
- method public long getStartTimeMillis();
- method public long getSuspendedTimeMillis();
- method public int getUid();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.InferenceInfo> CREATOR;
- }
-
- public static final class InferenceInfo.Builder {
- ctor public InferenceInfo.Builder(int);
- method @NonNull public android.app.ondeviceintelligence.InferenceInfo build();
- method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setEndTimeMillis(long);
- method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setStartTimeMillis(long);
- method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setSuspendedTimeMillis(long);
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceException(int, @NonNull String);
- ctor public OnDeviceIntelligenceException(int);
- method public int getErrorCode();
- method @NonNull public android.os.PersistableBundle getErrorParams();
- field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
- field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
- field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
- field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
- field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7
- field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5
- field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe
- field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6
- field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8
- field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4
- field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc
- field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb
- field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa
- field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
- field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
- field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
- field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ondeviceintelligence.InferenceInfo> getLatestInferenceInfo(long);
- method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
- field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
- field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
- method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
- method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
- method public void onResult(@NonNull android.os.Bundle);
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
- ctor public ProcessingSignal();
- method public void sendSignal(@NonNull android.os.PersistableBundle);
- method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback);
- }
-
- public static interface ProcessingSignal.OnProcessingSignalCallback {
- method public void onSignalReceived(@NonNull android.os.PersistableBundle);
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
- method public void onPartialResult(@NonNull android.os.Bundle);
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
- ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
- ctor public TokenInfo(long);
- method public int describeContents();
- method public long getCount();
- method @NonNull public android.os.PersistableBundle getInfoParams();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
- }
-
-}
-
package android.app.people {
public final class PeopleManager {
@@ -3300,6 +3162,14 @@ package android.app.usage {
}
+package android.app.wallpaper {
+
+ @FlaggedApi("android.app.live_wallpaper_content_handling") public final class WallpaperDescription implements android.os.Parcelable {
+ method @NonNull public android.util.SparseArray<android.graphics.Rect> getCropHints();
+ }
+
+}
+
package android.app.wallpapereffectsgeneration {
public final class CameraAttributes implements android.os.Parcelable {
@@ -3543,6 +3413,7 @@ package android.companion.virtual {
public static interface VirtualDeviceManager.ActivityListener {
method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender);
method public void onDisplayEmpty(int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowHidden(int);
method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowShown(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
@@ -5252,9 +5123,9 @@ package android.hardware.contexthub {
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
- method public void close();
- method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
- method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
+ method @Nullable public String getServiceDescriptor();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
@@ -5306,7 +5177,7 @@ package android.hardware.contexthub {
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
- method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
}
@@ -5455,19 +5326,13 @@ package android.hardware.display {
field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
}
- public abstract static class VirtualDisplay.Callback {
- method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
- }
-
public final class VirtualDisplayConfig implements android.os.Parcelable {
- method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions();
}
public static final class VirtualDisplayConfig.Builder {
- method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean);
@@ -6318,7 +6183,7 @@ package android.hardware.location {
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
@@ -7196,8 +7061,8 @@ package android.hardware.soundtrigger {
method public int getAudioCapabilities();
method @NonNull public byte[] getData();
method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphrases();
- method public boolean isAllowMultipleTriggers();
method public boolean isCaptureRequested();
+ method public boolean isMultipleTriggersAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
}
@@ -7205,11 +7070,11 @@ package android.hardware.soundtrigger {
public static final class SoundTrigger.RecognitionConfig.Builder {
ctor public SoundTrigger.RecognitionConfig.Builder();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
- method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setMultipleTriggersAllowed(boolean);
}
public static class SoundTrigger.RecognitionEvent {
@@ -7779,7 +7644,7 @@ package android.media {
}
public final class MediaCas implements java.lang.AutoCloseable {
- method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
+ method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
}
@@ -8122,14 +7987,17 @@ package android.media.musicrecognition {
package android.media.quality {
@FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager {
+ method public void addGlobalActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
method @NonNull public java.util.List<java.lang.String> getPictureProfileAllowList();
method @NonNull public java.util.List<java.lang.String> getPictureProfilePackageNames();
- method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String);
+ method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String, boolean);
method @NonNull public java.util.List<java.lang.String> getSoundProfileAllowList();
method @NonNull public java.util.List<java.lang.String> getSoundProfilePackageNames();
- method @NonNull public java.util.List<android.media.quality.SoundProfile> getSoundProfilesByPackage(@NonNull String);
+ method @NonNull public java.util.List<android.media.quality.SoundProfile> getSoundProfilesByPackage(@NonNull String, boolean);
method public void setAutoPictureQualityEnabled(boolean);
method public void setAutoSoundQualityEnabled(boolean);
+ method public boolean setDefaultPictureProfile(@Nullable String);
+ method public boolean setDefaultSoundProfile(@Nullable String);
method public void setPictureProfileAllowList(@NonNull java.util.List<java.lang.String>);
method public void setSoundProfileAllowList(@NonNull java.util.List<java.lang.String>);
method public void setSuperResolutionEnabled(boolean);
@@ -8692,8 +8560,8 @@ package android.media.tv.tuner {
method public int setLnaEnabled(boolean);
method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
- method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
+ method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
@@ -10618,28 +10486,6 @@ package android.net.util {
}
-package android.net.vcn {
-
- public class VcnManager {
- method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
- method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
- }
-
- public static interface VcnManager.VcnNetworkPolicyChangeListener {
- method public void onPolicyChanged();
- }
-
- public final class VcnNetworkPolicyResult implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
- method public boolean isTeardownRequested();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
- }
-
-}
-
package android.net.wifi {
public final class WifiKeystore {
@@ -12629,6 +12475,7 @@ package android.provider {
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+ field @FlaggedApi("com.android.internal.telephony.flags.action_sim_preference_settings") public static final String ACTION_SIM_PREFERENCE_SETTINGS = "android.settings.SIM_PREFERENCE_SETTINGS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
@@ -13803,39 +13650,6 @@ package android.service.oemlock {
}
-package android.service.ondeviceintelligence {
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
- ctor public OnDeviceIntelligenceService();
- method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
- method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
- method public abstract void onInferenceServiceConnected();
- method public abstract void onInferenceServiceDisconnected();
- method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
- ctor public OnDeviceSandboxedInferenceService();
- method public final void fetchFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
- method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
- method public final void getReadOnlyFileDescriptor(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.ParcelFileDescriptor>) throws java.io.FileNotFoundException;
- method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
- method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
- method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
- method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
- field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
- }
-
-}
-
package android.service.persistentdata {
@FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
@@ -16095,6 +15909,10 @@ package android.telephony {
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4
field public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6
field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44; // 0x2c
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43; // 0x2b
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42; // 0x2a
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45; // 0x2d
field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; // 0x2f
field @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb
field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5
@@ -16139,6 +15957,13 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static interface TelephonyCallback.CarrierRoamingNtnModeListener {
+ method public default void onCarrierRoamingNtnAvailableServicesChanged(@NonNull int[]);
+ method public default void onCarrierRoamingNtnEligibleStateChanged(boolean);
+ method public void onCarrierRoamingNtnModeChanged(boolean);
+ method public default void onCarrierRoamingNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public static interface TelephonyCallback.CellularIdentifierDisclosedListener {
method public void onCellularIdentifierDisclosedChanged(@NonNull android.telephony.CellularIdentifierDisclosure);
}
@@ -16264,6 +16089,7 @@ package android.telephony {
method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
+ method @FlaggedApi("com.android.internal.telephony.flags.get_group_id_level2") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getGroupIdLevel2();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<java.lang.String> getImsPcscfAddresses();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @Nullable @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER) public String getImsPrivateUserIdentity();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<android.net.Uri> getImsPublicUserIdentities();
@@ -18741,6 +18567,14 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class EarfcnRange implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=0, to=65535) public int getEndEarfcn();
+ method @IntRange(from=0, to=65535) public int getStartEarfcn();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.EarfcnRange> CREATOR;
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public class EnableRequestAttributes {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isDemoMode();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEmergencyMode();
@@ -18779,6 +18613,14 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteAccessConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.telephony.satellite.SatelliteInfo> getSatelliteInfos();
+ method @NonNull public java.util.List<java.lang.Integer> getTagIds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteAccessConfiguration> CREATOR;
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
@@ -18793,6 +18635,11 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilities);
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteCommunicationAllowedStateCallback {
+ method public default void onSatelliteAccessConfigurationChanged(@Nullable android.telephony.satellite.SatelliteAccessConfiguration);
+ method public void onSatelliteCommunicationAllowedStateChanged(boolean);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram();
@@ -18804,18 +18651,39 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteDisallowedReasonsCallback {
+ method public void onSatelliteDisallowedReasonsChanged(@NonNull int[]);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.Integer> getBands();
+ method @NonNull public java.util.List<android.telephony.satellite.EarfcnRange> getEarfcnRanges();
+ method @NonNull public java.util.UUID getSatelliteId();
+ method @NonNull public android.telephony.satellite.SatellitePosition getSatellitePosition();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteInfo> CREATOR;
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCommunicationAllowedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCommunicationAllowedStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForIncomingDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForSatelliteDisallowedReasonsChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSelectedNbIotSatelliteSubscriptionChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSupportedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -18828,16 +18696,26 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAccessConfigurationForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteAccessConfiguration,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteDisplayName(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.CharSequence,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteSubscriberProvisionStatus(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSelectedNbIotSatelliteSubscriptionId(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Integer,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForCommunicationAllowedStateChanged(@NonNull android.telephony.satellite.SatelliteCommunicationAllowedStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForIncomingDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDisallowedReasonsChanged(@NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(@NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSupportedStateChanged(@NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION = "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED = "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; // 0x3
field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; // 0x5
@@ -18856,6 +18734,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT = "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
@@ -18905,6 +18784,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30; // 0x1e
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9
@@ -18922,18 +18802,104 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteModemEnableRequestAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriptionInfo getSatelliteSubscriptionInfo();
+ method public boolean isDemoMode();
+ method public boolean isEmergencyMode();
+ method public boolean isEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteModemEnableRequestAttributes> CREATOR;
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteModemStateCallback {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatellitePosition implements android.os.Parcelable {
+ method public int describeContents();
+ method public double getAltitudeKm();
+ method public double getLongitudeDegrees();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatellitePosition> CREATOR;
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSatelliteSubscriptionProvisionStateChanged(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriberInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getCarrierId();
+ method @NonNull public String getNiddApn();
+ method public int getSubId();
+ method @NonNull public String getSubscriberId();
+ method public int getSubscriberIdType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriberInfo> CREATOR;
+ field public static final int ICCID = 0; // 0x0
+ field public static final int IMSI_MSISDN = 1; // 0x1
+ }
+
+ public static final class SatelliteSubscriberInfo.Builder {
+ ctor public SatelliteSubscriberInfo.Builder();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo build();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setCarrierId(int);
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setNiddApn(@NonNull String);
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubId(int);
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubscriberId(@NonNull String);
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubscriberIdType(int);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriberProvisionStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo getSatelliteSubscriberInfo();
+ method public boolean isProvisioned();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriberProvisionStatus> CREATOR;
+ }
+
+ public static final class SatelliteSubscriberProvisionStatus.Builder {
+ ctor public SatelliteSubscriberProvisionStatus.Builder();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus build();
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus.Builder setProvisioned(boolean);
+ method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus.Builder setSatelliteSubscriberInfo(@NonNull android.telephony.satellite.SatelliteSubscriberInfo);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriptionInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getIccId();
+ method @NonNull public String getNiddApn();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriptionInfo> CREATOR;
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteSupportedStateCallback {
+ method public void onSatelliteSupportedStateChanged(boolean);
}
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramRequested(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramStateChanged(int, int, int, int);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SelectedNbIotSatelliteSubscriptionCallback {
+ method public void onSelectedNbIotSatelliteSubscriptionChanged(int);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SystemSelectionSpecifier implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int[] getBands();
+ method @NonNull public int[] getEarfcns();
+ method @NonNull public String getMccMnc();
+ method @NonNull public java.util.List<android.telephony.satellite.SatelliteInfo> getSatelliteInfos();
+ method @NonNull public int[] getTagIds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SystemSelectionSpecifier> CREATOR;
}
}
@@ -19041,8 +19007,10 @@ package android.view {
public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
method public final long getUserActivityTimeout();
+ method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public boolean isReceivePowerKeyDoublePressEnabled();
method public boolean isSystemApplicationOverlay();
method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>);
+ method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public void setReceivePowerKeyDoublePressEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
method public final void setUserActivityTimeout(long);
field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 967f6194969e..6230a59a62c0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,6 +541,7 @@ package android.app {
method @Nullable public android.graphics.Rect peekBitmapDimensions();
method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable android.graphics.Bitmap, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
+ method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithDescription(@Nullable android.graphics.Bitmap, @NonNull android.app.wallpaper.WallpaperDescription, boolean, int) throws java.io.IOException;
method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
method public boolean shouldEnableWideColorGamut();
@@ -656,6 +657,7 @@ package android.app.admin {
field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e
field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final int OPERATION_SET_APP_FUNCTIONS_POLICY = 42; // 0x2a
field public static final int OPERATION_SET_CAMERA_DISABLED = 31; // 0x1f
field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; // 0x29
field public static final int OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY = 32; // 0x20
@@ -907,6 +909,15 @@ package android.app.usage {
}
+package android.app.wallpaper {
+
+ public static final class WallpaperDescription.Builder {
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>);
+ }
+
+}
+
package android.appwidget {
public class AppWidgetManager {
@@ -923,6 +934,7 @@ package android.companion {
method @NonNull public android.companion.AssociationInfo build();
method @NonNull public android.companion.AssociationInfo.Builder setAssociatedDevice(@Nullable android.companion.AssociatedDevice);
method @FlaggedApi("android.companion.association_device_icon") @NonNull public android.companion.AssociationInfo.Builder setDeviceIcon(@Nullable android.graphics.drawable.Icon);
+ method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setDeviceId(@Nullable android.companion.DeviceId);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceMacAddress(@Nullable android.net.MacAddress);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
@@ -931,7 +943,6 @@ package android.companion {
method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
- method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
@@ -3236,14 +3247,6 @@ package android.service.notification {
}
-package android.service.ondeviceintelligence {
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
- method public void onReady();
- }
-
-}
-
package android.service.quickaccesswallet {
public interface QuickAccessWalletClient extends java.io.Closeable {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 9140bdf4fba7..349b4edffc32 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,10 +1,4 @@
// Baseline format: 1.0
-ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE:
- Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
-ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
- Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
-
-
BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
Methods must not throw unchecked exceptions
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index d1eb8e8fa2ff..4bf87f91cb2f 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -141,6 +141,14 @@ public abstract class Animator implements Cloneable {
}
/**
+ * @see #sPostNotifyEndListenerEnabled
+ * @hide
+ */
+ public static boolean isPostNotifyEndListenerEnabled() {
+ return sPostNotifyEndListenerEnabled;
+ }
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 38aea64386a0..03ef669c0675 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1279,7 +1279,24 @@ public class Activity extends ContextThemeWrapper
*
* <p>This method should be utilized when an activity wants to nudge the user to switch
* to the web application in cases where the web may provide the user with a better
- * experience. Note that this method does not guarantee that the education will be shown.</p>
+ * experience. Note that this method does not guarantee that the education will be shown.
+ *
+ * <p>The number of times that the "Open in browser" education can be triggered by this method
+ * is limited per application, and, when shown, the education appears above the app's content.
+ * For these reasons, developers should use this method sparingly when it is least
+ * disruptive to the user to show the education and when it is optimal to switch the user to a
+ * browser session. Before requesting to show the education, developers should assert that they
+ * have set a link that can be used by the "Open in browser" feature through either
+ * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or
+ * {@link AssistContent#setWebUri} so that users are navigated to a relevant page if they choose
+ * to switch to the browser. If a URI is not set using either method, "Open in browser" will
+ * utilize a generic link if available which will direct users to the homepage of the site
+ * associated with the app. The generic link is provided for a limited number of applications by
+ * the system and cannot be edited by developers. If none of these options contains a valid URI,
+ * the user will not be provided with the option to switch to the browser and the education will
+ * not be shown if requested.
+ *
+ * @see android.app.assist.AssistContent#EXTRA_SESSION_TRANSFER_WEB_URI
*/
@FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
public final void requestOpenInBrowserEducation() {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 33ba05865042..69d3e8d4c0d2 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1189,6 +1189,18 @@ public class ActivityManager {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
}
+ /** @hide Should this process state be considered jank perceptible? */
+ public static final boolean isProcStateJankPerceptible(int procState) {
+ if (Flags.jankPerceptibleNarrow()) {
+ return procState == PROCESS_STATE_PERSISTENT_UI
+ || procState == PROCESS_STATE_TOP
+ || procState == PROCESS_STATE_IMPORTANT_FOREGROUND
+ || procState == PROCESS_STATE_TOP_SLEEPING;
+ } else {
+ return !isProcStateCached(procState);
+ }
+ }
+
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index eccb6ffb281c..abdfb53537f8 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -44,6 +44,8 @@ import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
import android.os.TransactionTooLargeException;
import android.os.WorkSource;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
import android.util.ArraySet;
import android.util.Pair;
@@ -1352,6 +1354,14 @@ public abstract class ActivityManagerInternal {
String reason, int exitInfoReason);
/**
+ * Queries the offset data for a given method on a process.
+ * @hide
+ */
+ public abstract void getExecutableMethodFileOffsets(@NonNull String processName,
+ int pid, int uid, @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback callback);
+
+ /**
* Add a creator token for all embedded intents (stored as extra) of the given intent.
*
* @param intent The given intent
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f8186d68e210..48b74f2d0776 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -165,6 +165,10 @@ import android.os.TelephonyServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.MethodDescriptorParser;
import android.permission.IPermissionManager;
import android.provider.BlockedNumberContract;
import android.provider.CalendarContract;
@@ -1372,7 +1376,8 @@ public final class ActivityThread extends ClientTransactionHandler
data.startRequestedElapsedTime = startRequestedElapsedTime;
data.startRequestedUptime = startRequestedUptime;
updateCompatOverrideScale(compatInfo);
- CompatibilityInfo.applyOverrideScaleIfNeeded(config);
+ updateCompatOverrideDisplayRotation(compatInfo);
+ CompatibilityInfo.applyOverrideIfNeeded(config);
sendMessage(H.BIND_APPLICATION, data);
}
@@ -1386,6 +1391,15 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ private void updateCompatOverrideDisplayRotation(@NonNull CompatibilityInfo info) {
+ if (info.isOverrideDisplayRotationRequired()) {
+ CompatibilityInfo.setOverrideDisplayRotation(info.applicationDisplayRotation);
+ } else {
+ CompatibilityInfo.setOverrideDisplayRotation(
+ WindowConfiguration.ROTATION_UNDEFINED);
+ }
+ }
+
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = entryPoint;
@@ -2036,6 +2050,7 @@ public final class ActivityThread extends ClientTransactionHandler
ucd.pkg = pkg;
ucd.info = info;
updateCompatOverrideScale(info);
+ updateCompatOverrideDisplayRotation(info);
sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
}
@@ -2225,6 +2240,29 @@ public final class ActivityThread extends ClientTransactionHandler
args.arg6 = uiTranslationSpec;
sendMessage(H.UPDATE_UI_TRANSLATION_STATE, args);
}
+
+ @Override
+ public void getExecutableMethodFileOffsets(
+ @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback resultCallback) {
+ Method method = MethodDescriptorParser.parseMethodDescriptor(
+ getClass().getClassLoader(), methodDescriptor);
+ VMDebug.ExecutableMethodFileOffsets location =
+ VMDebug.getExecutableMethodFileOffsets(method);
+ try {
+ if (location == null) {
+ resultCallback.onResult(null);
+ return;
+ }
+ ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+ ret.containerPath = location.getContainerPath();
+ ret.containerOffset = location.getContainerOffset();
+ ret.methodOffset = location.getMethodOffset();
+ resultCallback.onResult(ret);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
private @NonNull SafeCancellationTransport createSafeCancellationTransport(
@@ -3907,12 +3945,7 @@ public final class ActivityThread extends ClientTransactionHandler
if (mLastProcessState == processState) {
return;
}
- // Do not issue a transitional GC if we are transitioning between 2 cached states.
- // Only update if the state flips between cached and uncached or vice versa
- if (ActivityManager.isProcStateCached(mLastProcessState)
- != ActivityManager.isProcStateCached(processState)) {
- updateVmProcessState(processState);
- }
+ updateVmProcessState(mLastProcessState, processState);
mLastProcessState = processState;
if (localLOGV) {
Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
@@ -3921,18 +3954,21 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ /** Converts a process state to a VM process state. */
+ private static int toVmProcessState(int processState) {
+ final int state = ActivityManager.isProcStateJankPerceptible(processState)
+ ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
+ : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
+ return state;
+ }
+
/** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
- // Currently ART VM only uses state updates for Transitional GC, and thus
- // this function initiates a Transitional GC for transitions into Cached apps states.
- private void updateVmProcessState(int processState) {
- // Only a transition into Cached state should result in a Transitional GC request
- // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
- // Note that there are 4 possible cached states currently, all of which are
- // JANK_IMPERCEPTIBLE from GC point of view.
- final int state = ActivityManager.isProcStateCached(processState)
- ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
- : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
- VMRuntime.getRuntime().updateProcessState(state);
+ private void updateVmProcessState(int lastProcessState, int newProcessState) {
+ final int state = toVmProcessState(newProcessState);
+ if (lastProcessState == PROCESS_STATE_UNKNOWN
+ || state != toVmProcessState(lastProcessState)) {
+ VMRuntime.getRuntime().updateProcessState(state);
+ }
}
@Override
@@ -5159,7 +5195,7 @@ public final class ActivityThread extends ClientTransactionHandler
try {
if (doRebind) {
ActivityManager.getService().unbindFinished(
- data.token, data.intent, doRebind);
+ data.token, data.intent);
} else {
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0, data.intent);
@@ -6995,21 +7031,44 @@ public final class ActivityThread extends ClientTransactionHandler
final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
if (start) {
- try {
- switch (profileType) {
- default:
+ switch (profileType) {
+ case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
+ if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
+ Slog.w(TAG, "Low overhead tracing feature is not enabled");
+ break;
+ }
+ VMDebug.startLowOverheadTrace();
+ break;
+ default:
+ try {
mProfiler.setProfiler(profilerInfo);
mProfiler.startProfiling();
break;
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
- + " -- can the process access this path?");
- } finally {
- profilerInfo.closeFd();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
+ + " -- can the process access this path?");
+ } finally {
+ profilerInfo.closeFd();
+ }
}
} else {
switch (profileType) {
+ case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
+ if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
+ if (profilerInfo != null) {
+ profilerInfo.closeFd();
+ }
+ Slog.w(TAG, "Low overhead tracing feature is not enabled");
+ break;
+ }
+ if (profilerInfo != null) {
+ FileDescriptor fd = profilerInfo.profileFd.getFileDescriptor();
+ VMDebug.TraceDestination dst =
+ VMDebug.TraceDestination.fromFileDescriptor(fd);
+ VMDebug.dumpLowOverheadTrace(dst);
+ }
+ VMDebug.stopLowOverheadTrace();
+ break;
default:
mProfiler.stopProfiling();
break;
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 009cd7249dcd..61b56877589b 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -21,6 +21,7 @@ import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -69,6 +70,14 @@ public class AppCompatTaskInfo implements Parcelable {
public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
/**
+ * Contains the top activity bounds when the activity is letterboxed.
+ * It's {@code null} if there's no top activity in the task or it's not letterboxed.
+ */
+ // TODO(b/379824541) Remove duplicate information.
+ @Nullable
+ public Rect topActivityLetterboxBounds;
+
+ /**
* Stores camera-related app compat information about a particular Task.
*/
public CameraCompatTaskInfo cameraCompatTaskInfo = CameraCompatTaskInfo.create();
@@ -378,6 +387,7 @@ public class AppCompatTaskInfo implements Parcelable {
topActivityLetterboxHeight = source.readInt();
topActivityLetterboxAppWidth = source.readInt();
topActivityLetterboxAppHeight = source.readInt();
+ topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR);
cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
}
@@ -393,6 +403,7 @@ public class AppCompatTaskInfo implements Parcelable {
dest.writeInt(topActivityLetterboxHeight);
dest.writeInt(topActivityLetterboxAppWidth);
dest.writeInt(topActivityLetterboxAppHeight);
+ dest.writeTypedObject(topActivityLetterboxBounds, flags);
dest.writeTypedObject(cameraCompatTaskInfo, flags);
}
@@ -415,6 +426,7 @@ public class AppCompatTaskInfo implements Parcelable {
+ " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
+ " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
+ " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
+ + " topActivityLetterboxBounds=" + topActivityLetterboxBounds
+ " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
+ "}";
}
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index b4dee2e937cb..56ed290baf2e 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,6 +19,7 @@ package android.app;
parcelable AppOpsManager.PackageOps;
parcelable AppOpsManager.NoteOpEventProxyInfo;
parcelable AppOpsManager.NoteOpEvent;
+parcelable AppOpsManager.NotedOp;
parcelable AppOpsManager.OpFeatureEntry;
parcelable AppOpsManager.OpEntry;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 34765781d105..c789e28cdbab 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -262,6 +262,24 @@ public class AppOpsManager {
private static final Object sLock = new Object();
+ // A map that records noted times for each op.
+ private final ArrayMap<NotedOp, Integer> mPendingNotedOps = new ArrayMap<>();
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+ private static final int NOTE_OP_BATCHING_DELAY_MILLIS = 1000;
+
+ private boolean isNoteOpBatchingSupported() {
+ // If noteOp is called from system server no IPC is made, hence we don't need batching.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return false;
+ }
+ return Flags.noteOpBatchingEnabled();
+ }
+
+ private final Object mBatchedNoteOpLock = new Object();
+ @GuardedBy("mBatchedNoteOpLock")
+ private boolean mIsBatchedNoteOpCallScheduled = false;
+
/** Current {@link OnOpNotedCallback}. Change via {@link #setOnOpNotedCallback} */
@GuardedBy("sLock")
private static @Nullable OnOpNotedCallback sOnOpNotedCallback;
@@ -3586,7 +3604,7 @@ public class AppOpsManager {
/** Attribution tag of the proxy that noted the op */
private @Nullable String mAttributionTag;
/** Persistent device Id of the proxy that noted the op */
- private @Nullable String mDeviceId;
+ private @NonNull String mDeviceId;
/**
* Reinit existing object with new state.
@@ -3599,7 +3617,7 @@ public class AppOpsManager {
* @hide
*/
public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable String deviceId) {
+ @Nullable String attributionTag, @NonNull String deviceId) {
mUid = Preconditions.checkArgumentNonnegative(uid);
mPackageName = packageName;
mAttributionTag = attributionTag;
@@ -3662,7 +3680,8 @@ public class AppOpsManager {
"from", 0);
this.mPackageName = packageName;
this.mAttributionTag = attributionTag;
- this.mDeviceId = deviceId;
+ this.mDeviceId = deviceId == null ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ : deviceId;
}
/**
* Copy constructor
@@ -3705,7 +3724,7 @@ public class AppOpsManager {
* Persistent device Id of the proxy that noted the op
*/
@FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
- public @Nullable String getDeviceId() { return mDeviceId; }
+ public @NonNull String getDeviceId() { return mDeviceId; }
@Override
@DataClass.Generated.Member
@@ -3716,12 +3735,12 @@ public class AppOpsManager {
byte flg = 0;
if (mPackageName != null) flg |= 0x2;
if (mAttributionTag != null) flg |= 0x4;
- if (mDeviceId != null) flg |= 0x8;
+ flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUid);
if (mPackageName != null) dest.writeString(mPackageName);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
- if (mDeviceId != null) dest.writeString(mDeviceId);
+ dest.writeString(mDeviceId);
}
@Override
@@ -3739,7 +3758,8 @@ public class AppOpsManager {
int uid = in.readInt();
String packageName = (flg & 0x2) == 0 ? null : in.readString();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
- String deviceId = (flg & 0x8) == 0 ? null : in.readString();
+ String deviceId = (flg & 0x8) == 0 ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ : in.readString();
this.mUid = uid;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mUid,
@@ -7464,6 +7484,135 @@ public class AppOpsManager {
}
/**
+ * A NotedOp is an app op grouped in noteOp API and sent to the system server in a batch
+ *
+ * @hide
+ */
+ public static final class NotedOp implements Parcelable {
+ private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+ private final @IntRange(from = 0) int mUid;
+ private final @Nullable String mPackageName;
+ private final @Nullable String mAttributionTag;
+ private final int mVirtualDeviceId;
+ private final @Nullable String mMessage;
+ private final boolean mShouldCollectAsyncNotedOp;
+ private final boolean mShouldCollectMessage;
+
+ public NotedOp(int op, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+ boolean shouldCollectAsyncNotedOp, boolean shouldCollectMessage) {
+ mOp = op;
+ mUid = uid;
+ mPackageName = packageName;
+ mAttributionTag = attributionTag;
+ mVirtualDeviceId = virtualDeviceId;
+ mMessage = message;
+ mShouldCollectAsyncNotedOp = shouldCollectAsyncNotedOp;
+ mShouldCollectMessage = shouldCollectMessage;
+ }
+
+ NotedOp(Parcel source) {
+ mOp = source.readInt();
+ mUid = source.readInt();
+ mPackageName = source.readString();
+ mAttributionTag = source.readString();
+ mVirtualDeviceId = source.readInt();
+ mMessage = source.readString();
+ mShouldCollectAsyncNotedOp = source.readBoolean();
+ mShouldCollectMessage = source.readBoolean();
+ }
+
+ public int getOp() {
+ return mOp;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ public @Nullable String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ public int getVirtualDeviceId() {
+ return mVirtualDeviceId;
+ }
+
+ public @Nullable String getMessage() {
+ return mMessage;
+ }
+
+ public boolean getShouldCollectAsyncNotedOp() {
+ return mShouldCollectAsyncNotedOp;
+ }
+
+ public boolean getShouldCollectMessage() {
+ return mShouldCollectMessage;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOp);
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeString(mAttributionTag);
+ dest.writeInt(mVirtualDeviceId);
+ dest.writeString(mMessage);
+ dest.writeBoolean(mShouldCollectAsyncNotedOp);
+ dest.writeBoolean(mShouldCollectMessage);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NotedOp that = (NotedOp) o;
+ return mOp == that.mOp && mUid == that.mUid && Objects.equals(mPackageName,
+ that.mPackageName) && Objects.equals(mAttributionTag, that.mAttributionTag)
+ && mVirtualDeviceId == that.mVirtualDeviceId && Objects.equals(mMessage,
+ that.mMessage) && Objects.equals(mShouldCollectAsyncNotedOp,
+ that.mShouldCollectAsyncNotedOp) && Objects.equals(mShouldCollectMessage,
+ that.mShouldCollectMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mOp, mUid, mPackageName, mAttributionTag, mVirtualDeviceId,
+ mMessage, mShouldCollectAsyncNotedOp, mShouldCollectMessage);
+ }
+
+ @Override
+ public String toString() {
+ return "NotedOp{" + "mOp=" + mOp + ", mUid=" + mUid + ", mPackageName=" + mPackageName
+ + ", mAttributionTag=" + mAttributionTag + ", mVirtualDeviceId="
+ + mVirtualDeviceId + ", mMessage=" + mMessage + ", mShouldCollectAsyncNotedOp="
+ + mShouldCollectAsyncNotedOp + ", mShouldCollectMessage="
+ + mShouldCollectMessage + "}";
+ }
+
+
+ public static final @NonNull Creator<NotedOp> CREATOR =
+ new Creator<>() {
+ @Override public NotedOp createFromParcel(Parcel source) {
+ return new NotedOp(source);
+ }
+
+ @Override public NotedOp[] newArray(int size) {
+ return new NotedOp[size];
+ }
+ };
+ }
+
+ /**
* Computes the sum of the counts for the given flags in between the begin and
* end UID states.
*
@@ -7977,6 +8126,9 @@ public class AppOpsManager {
AppOpsManager(Context context, IAppOpsService service) {
mContext = context;
mService = service;
+ mHandlerThread = new HandlerThread("AppOpsManager");
+ mHandlerThread.start();
+ mHandler = mHandlerThread.getThreadHandler();
if (mContext != null) {
final PackageManager pm = mContext.getPackageManager();
@@ -9313,15 +9465,74 @@ public class AppOpsManager {
}
}
- SyncNotedAppOp syncOp;
- if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
- syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
- } else {
- syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
- virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ SyncNotedAppOp syncOp = null;
+ boolean skipBinderCall = false;
+ if (isNoteOpBatchingSupported()) {
+ int mode = sAppOpModeCache.query(
+ new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag,
+ "noteOpNoThrow"));
+ // For FOREGROUND mode, we still need to make a binder call to the system service
+ // to translate it to ALLOWED or IGNORED. So no batching is needed.
+ if (mode != MODE_FOREGROUND) {
+ synchronized (mBatchedNoteOpLock) {
+ NotedOp notedOp = new NotedOp(op, uid, packageName, attributionTag,
+ virtualDeviceId, message, collectionMode == COLLECT_ASYNC,
+ shouldCollectMessage);
+
+ // Batch same noteOp calls and send them with their counters to the system
+ // service asynchronously. The time window for batching is specified in
+ // NOTE_OP_BATCHING_DELAY_MILLIS. Always allow the first noteOp call to go
+ // through binder API. Accumulate subsequent same noteOp calls during the
+ // time window in mPendingNotedOps.
+ if (!mPendingNotedOps.containsKey(notedOp)) {
+ mPendingNotedOps.put(notedOp, 0);
+ } else {
+ skipBinderCall = true;
+ mPendingNotedOps.merge(notedOp, 1, Integer::sum);
+ }
+
+ if (!mIsBatchedNoteOpCallScheduled) {
+ mHandler.postDelayed(() -> {
+ ArrayMap<NotedOp, Integer> pendingNotedOpsCopy;
+ synchronized(mBatchedNoteOpLock) {
+ mIsBatchedNoteOpCallScheduled = false;
+ pendingNotedOpsCopy =
+ new ArrayMap<NotedOp, Integer>(mPendingNotedOps);
+ mPendingNotedOps.clear();
+ }
+ for (int i = pendingNotedOpsCopy.size() - 1; i >= 0; i--) {
+ if (pendingNotedOpsCopy.valueAt(i) == 0) {
+ pendingNotedOpsCopy.removeAt(i);
+ }
+ }
+ if (!pendingNotedOpsCopy.isEmpty()) {
+ try {
+ mService.noteOperationsInBatch(pendingNotedOpsCopy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }, NOTE_OP_BATCHING_DELAY_MILLIS);
+
+ mIsBatchedNoteOpCallScheduled = true;
+ }
+ }
+
+ syncOp = new SyncNotedAppOp(mode, op, attributionTag, packageName);
+ }
+ }
+
+ if (!skipBinderCall) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
+ collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+ } else {
+ syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
+ virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage);
+ }
}
+
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
collectNotedOpForSelf(syncOp);
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b21defbcc0e3..8b7ea0f8b46a 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -29,7 +29,7 @@ import com.android.internal.app.IAppOpsCallback;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.UndecFunction;
@@ -86,9 +86,9 @@ public abstract class AppOpsManagerInternal {
*/
SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl);
+ @Nullable String message, boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+ Boolean, Integer, SyncNotedAppOp> superImpl);
/**
* Allows overriding note proxy operation behavior.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 3cbea87e135e..da338474a448 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -798,7 +798,7 @@ public class ApplicationPackageManager extends PackageManager {
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
- .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+ .api(HAS_SYSTEM_FEATURE_API).maxEntries(SDK_FEATURE_COUNT).isolateUids(false),
HAS_SYSTEM_FEATURE_API, null) {
@Override
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
new file mode 100644
index 000000000000..fe2e10752355
--- /dev/null
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -0,0 +1,215 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context.RegisterReceiverFlags;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.nsd.NsdManager;
+import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.IpcDataCache;
+import android.os.IpcDataCache.Config;
+import android.os.UpdateLock;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.view.WindowManagerPolicyConstants;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+/** @hide */
+public class BroadcastStickyCache {
+
+ @VisibleForTesting
+ public static final String[] STICKY_BROADCAST_ACTIONS = {
+ AudioManager.ACTION_HDMI_AUDIO_PLUG,
+ AudioManager.ACTION_HEADSET_PLUG,
+ AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+ AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.RINGER_MODE_CHANGED_ACTION,
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ Intent.ACTION_BATTERY_CHANGED,
+ Intent.ACTION_DEVICE_STORAGE_FULL,
+ Intent.ACTION_DEVICE_STORAGE_LOW,
+ Intent.ACTION_SIM_STATE_CHANGED,
+ NsdManager.ACTION_NSD_STATE_CHANGED,
+ TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+ TetheringManager.ACTION_TETHER_STATE_CHANGED,
+ UpdateLock.UPDATE_LOCK_CHANGED,
+ UsbManager.ACTION_USB_STATE,
+ WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+ WifiManager.WIFI_STATE_CHANGED_ACTION,
+ WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
+ WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
+ "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
+ };
+
+ @VisibleForTesting
+ public static final ArrayMap<String, String> sActionApiNameMap = new ArrayMap<>();
+
+ private static final ArrayMap<String, IpcDataCache.Config> sActionConfigMap = new ArrayMap<>();
+
+ private static final ArrayMap<StickyBroadcastFilter, IpcDataCache<Void, Intent>>
+ sFilterCacheMap = new ArrayMap<>();
+
+ static {
+ sActionApiNameMap.put(AudioManager.ACTION_HDMI_AUDIO_PLUG, "hdmi_audio_plug");
+ sActionApiNameMap.put(AudioManager.ACTION_HEADSET_PLUG, "headset_plug");
+ sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+ "sco_audio_state_changed");
+ sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+ "action_sco_audio_state_updated");
+ sActionApiNameMap.put(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ "internal_ringer_mode_changed_action");
+ sActionApiNameMap.put(AudioManager.RINGER_MODE_CHANGED_ACTION,
+ "ringer_mode_changed");
+ sActionApiNameMap.put(ConnectivityManager.CONNECTIVITY_ACTION,
+ "connectivity_change");
+ sActionApiNameMap.put(Intent.ACTION_BATTERY_CHANGED, "battery_changed");
+ sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_FULL, "device_storage_full");
+ sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, "device_storage_low");
+ sActionApiNameMap.put(Intent.ACTION_SIM_STATE_CHANGED, "sim_state_changed");
+ sActionApiNameMap.put(NsdManager.ACTION_NSD_STATE_CHANGED, "nsd_state_changed");
+ sActionApiNameMap.put(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+ "service_providers_updated");
+ sActionApiNameMap.put(TetheringManager.ACTION_TETHER_STATE_CHANGED,
+ "tether_state_changed");
+ sActionApiNameMap.put(UpdateLock.UPDATE_LOCK_CHANGED, "update_lock_changed");
+ sActionApiNameMap.put(UsbManager.ACTION_USB_STATE, "usb_state");
+ sActionApiNameMap.put(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+ "wifi_scan_availability_changed");
+ sActionApiNameMap.put(WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ "network_state_change");
+ sActionApiNameMap.put(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+ "supplicant_state_change");
+ sActionApiNameMap.put(WifiManager.WIFI_STATE_CHANGED_ACTION, "wifi_state_changed");
+ sActionApiNameMap.put(
+ WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, "wifi_p2p_state_changed");
+ sActionApiNameMap.put(
+ WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, "hdmi_plugged");
+ sActionApiNameMap.put(
+ "android.net.conn.INET_CONDITION_ACTION", "inet_condition_action");
+ }
+
+ /**
+ * Checks whether we can use caching for the given filter.
+ */
+ public static boolean useCache(@Nullable IntentFilter filter) {
+ return Flags.useStickyBcastCache()
+ && filter != null
+ && filter.safeCountActions() == 1
+ && ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, filter.getAction(0));
+ }
+
+ public static void invalidateCache(@NonNull String action) {
+ if (!Flags.useStickyBcastCache()
+ || !ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, action)) {
+ return;
+ }
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ sActionApiNameMap.get(action));
+ }
+
+ public static void invalidateAllCaches() {
+ for (int i = sActionApiNameMap.size() - 1; i >= 0; i--) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+ sActionApiNameMap.valueAt(i));
+ }
+ }
+
+ /**
+ * Returns the cached {@link Intent} based on the filter, if exits otherwise
+ * fetches the value from the service.
+ */
+ @Nullable
+ public static Intent getIntent(
+ @NonNull IApplicationThread applicationThread,
+ @NonNull String mBasePackageName,
+ @Nullable String attributionTag,
+ @NonNull IntentFilter filter,
+ @Nullable String broadcastPermission,
+ @UserIdInt int userId,
+ @RegisterReceiverFlags int flags) {
+ IpcDataCache<Void, Intent> intentDataCache = findIpcDataCache(filter);
+
+ if (intentDataCache == null) {
+ final String action = filter.getAction(0);
+ final StickyBroadcastFilter stickyBroadcastFilter =
+ new StickyBroadcastFilter(filter, action);
+ final Config config = getConfig(action);
+
+ intentDataCache =
+ new IpcDataCache<>(config,
+ (query) -> ActivityManager.getService().registerReceiverWithFeature(
+ applicationThread,
+ mBasePackageName,
+ attributionTag,
+ /* receiverId= */ "null",
+ /* receiver= */ null,
+ filter,
+ broadcastPermission,
+ userId,
+ flags));
+ sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache);
+ }
+ return intentDataCache.query(null);
+ }
+
+ @VisibleForTesting
+ public static void clearCacheForTest() {
+ sFilterCacheMap.clear();
+ }
+
+ @Nullable
+ private static IpcDataCache<Void, Intent> findIpcDataCache(
+ @NonNull IntentFilter filter) {
+ for (int i = sFilterCacheMap.size() - 1; i >= 0; i--) {
+ StickyBroadcastFilter existingFilter = sFilterCacheMap.keyAt(i);
+ if (filter.getAction(0).equals(existingFilter.action())
+ && IntentFilter.filterEquals(existingFilter.filter(), filter)) {
+ return sFilterCacheMap.valueAt(i);
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ private static IpcDataCache.Config getConfig(@NonNull String action) {
+ if (!sActionConfigMap.containsKey(action)) {
+ // We only need 1 entry per cache but just to be on the safer side we are choosing 32
+ // although we don't expect more than 1.
+ sActionConfigMap.put(action,
+ new Config(32, IpcDataCache.MODULE_SYSTEM, sActionApiNameMap.get(action)));
+ }
+
+ return sActionConfigMap.get(action);
+ }
+
+ @VisibleForTesting
+ private record StickyBroadcastFilter(@NonNull IntentFilter filter, @NonNull String action) {
+ }
+}
diff --git a/core/java/android/app/CameraCompatTaskInfo.java b/core/java/android/app/CameraCompatTaskInfo.java
index 432a0da15a47..845d2acbaf9d 100644
--- a/core/java/android/app/CameraCompatTaskInfo.java
+++ b/core/java/android/app/CameraCompatTaskInfo.java
@@ -16,11 +16,16 @@
package android.app;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.Surface;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -153,6 +158,27 @@ public class CameraCompatTaskInfo implements Parcelable {
+ "}";
}
+ /**
+ * Returns the sandboxed display rotation based on the given {@code cameraCompatMode}.
+ *
+ * <p>This will be what the app likely expects in its requested orientation while running on a
+ * device with portrait natural orientation: `CAMERA_COMPAT_FREEFORM_PORTRAIT_*` is 0, and
+ * `CAMERA_COMPAT_FREEFORM_LANDSCAPE_*` is 90.
+ *
+ * @return {@link WindowConfiguration#ROTATION_UNDEFINED} if not in camera compat mode.
+ */
+ @Surface.Rotation
+ public static int getDisplayRotationFromCameraCompatMode(@FreeformCameraCompatMode int
+ cameraCompatMode) {
+ return switch (cameraCompatMode) {
+ case CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
+ CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT -> ROTATION_0;
+ case CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE,
+ CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT -> ROTATION_90;
+ default -> ROTATION_UNDEFINED;
+ };
+ }
+
/** Human readable version of the freeform camera compat mode. */
@NonNull
public static String freeformCameraCompatModeToString(
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cd56957ed5d1..dcbdc2348fbc 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1922,10 +1922,23 @@ class ContextImpl extends Context {
}
}
try {
- final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
- mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
- AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
- flags);
+ final Intent intent;
+ if (receiver == null && BroadcastStickyCache.useCache(filter)) {
+ intent = BroadcastStickyCache.getIntent(
+ mMainThread.getApplicationThread(),
+ mBasePackageName,
+ getAttributionTag(),
+ filter,
+ broadcastPermission,
+ userId,
+ flags);
+ } else {
+ intent = ActivityManager.getService().registerReceiverWithFeature(
+ mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+ AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
+ userId, flags);
+ }
+
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
// TODO: determine at registration time if caller is
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0668958b2d5c..ad01ad57b2d8 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -321,7 +321,7 @@ interface IActivityManager {
oneway void removeContentProvider(in IBinder connection, boolean stable);
@UnsupportedAppUsage
void setRequestedOrientation(in IBinder token, int requestedOrientation);
- void unbindFinished(in IBinder token, in Intent service, boolean doRebind);
+ void unbindFinished(in IBinder token, in Intent service);
@UnsupportedAppUsage
void setProcessImportant(in IBinder token, int pid, boolean isForeground, String reason);
void setServiceForeground(in ComponentName className, in IBinder token,
@@ -1028,4 +1028,14 @@ interface IActivityManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
boolean enabled, int reason, in String subReason, int source, long threshold);
+
+ /**
+ * Creates and returns a new IntentCreatorToken that keeps the creatorUid and refreshes key
+ * fields of the intent passed in.
+ *
+ * @param intent The intent with key fields out of sync of the IntentCreatorToken it contains.
+ * @hide
+ */
+ @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
+ IBinder refreshIntentCreatorToken(in Intent intent);
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 06d01ecfcf06..063501bf82a2 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -46,6 +46,8 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationSpec;
@@ -183,4 +185,6 @@ oneway interface IApplicationThread {
void scheduleTimeoutService(IBinder token, int startId);
void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType);
void schedulePing(in RemoteCallback pong);
+ void getExecutableMethodFileOffsets(in MethodDescriptor methodDescriptor,
+ in IOffsetCallback resultCallback);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7fcae45ea452..7e998d90e04f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -817,18 +817,18 @@ public class Notification implements Parcelable
R.layout.notification_2025_template_expanded_base,
R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
+ R.layout.notification_2025_template_conversation,
+ R.layout.notification_2025_template_collapsed_call,
+ R.layout.notification_2025_template_expanded_call,
R.layout.notification_2025_template_collapsed_messaging,
+ R.layout.notification_2025_template_expanded_messaging,
R.layout.notification_2025_template_collapsed_media,
- R.layout.notification_template_material_big_picture,
- R.layout.notification_template_material_big_text,
- R.layout.notification_template_material_inbox,
- R.layout.notification_template_material_big_messaging,
- R.layout.notification_template_material_conversation,
- R.layout.notification_template_material_big_media,
- R.layout.notification_template_material_call,
- R.layout.notification_template_material_big_call,
- R.layout.notification_template_header -> true;
- case R.layout.notification_template_material_progress -> Flags.apiRichOngoing();
+ R.layout.notification_2025_template_expanded_media,
+ R.layout.notification_2025_template_expanded_big_picture,
+ R.layout.notification_2025_template_expanded_big_text,
+ R.layout.notification_2025_template_expanded_inbox -> true;
+ case R.layout.notification_2025_template_expanded_progress
+ -> Flags.apiRichOngoing();
default -> false;
};
}
@@ -3221,7 +3221,6 @@ public class Notification implements Parcelable
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean containsCustomViews() {
return contentView != null
|| bigContentView != null
@@ -3235,7 +3234,6 @@ public class Notification implements Parcelable
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean hasTitle() {
return extras != null
&& (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
@@ -3245,7 +3243,7 @@ public class Notification implements Parcelable
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public boolean hasPromotableStyle() {
final Class<? extends Style> notificationStyle = getNotificationStyle();
@@ -3257,11 +3255,16 @@ public class Notification implements Parcelable
}
/**
- * @hide
+ * Returns whether the notification has all the characteristics that make it eligible for
+ * {@link #FLAG_PROMOTED_ONGOING}. This method does not factor in other criteria such user
+ * preferences for the app or channel. If this returns true, it does not guarantee that the
+ * notification will be assigned FLAG_PROMOTED_ONGOING by the system, but if this returns false,
+ * it will not.
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public boolean hasPromotableCharacteristics() {
return isColorizedRequested()
+ && isOngoingEvent()
&& hasTitle()
&& !isGroupSummary()
&& !containsCustomViews()
@@ -4158,6 +4161,13 @@ public class Notification implements Parcelable
/**
* @hide
*/
+ public boolean isOngoingEvent() {
+ return (flags & FLAG_ONGOING_EVENT) != 0;
+ }
+
+ /**
+ * @hide
+ */
public boolean hasCompletedProgress() {
// not a progress notification; can't be complete
if (!extras.containsKey(EXTRA_PROGRESS)
@@ -5964,7 +5974,7 @@ public class Notification implements Parcelable
private static void setHeaderlessVerticalMargins(RemoteViews contentView,
StandardTemplateParams p, boolean hasSecondLine) {
- if (!p.mHeaderless) {
+ if (Flags.notificationsRedesignTemplates() || !p.mHeaderless) {
return;
}
int marginDimen = hasSecondLine
@@ -6445,10 +6455,13 @@ public class Notification implements Parcelable
// Clear view padding to allow buttons to start on the left edge.
// This must be done before 'setEmphasizedMode' which sets top/bottom margins.
big.setViewPadding(R.id.actions, 0, 0, 0, 0);
- // Add an optional indent that will make buttons start at the correct column when
- // there is enough space to do so (and fall back to the left edge if not).
- big.setInt(R.id.actions, "setCollapsibleIndentDimen",
- R.dimen.call_notification_collapsible_indent);
+ if (!Flags.notificationsRedesignTemplates()) {
+ // Add an optional indent that will make buttons start at the correct column
+ // when there is enough space to do so (and fall back to the left edge if not).
+ // This is handled directly in NotificationActionListLayout in the new design.
+ big.setInt(R.id.actions, "setCollapsibleIndentDimen",
+ R.dimen.call_notification_collapsible_indent);
+ }
if (evenlyDividedCallStyleActionLayout()) {
if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
Log.d(TAG, "setting evenly divided mode on action list");
@@ -6764,19 +6777,6 @@ public class Notification implements Parcelable
}
/**
- * Construct a RemoteViews for the ambient version of the notification.
- *
- * @hide
- */
- public RemoteViews makeAmbientNotification() {
- RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
- if (headsUpContentView != null) {
- return headsUpContentView;
- }
- return createContentView();
- }
-
- /**
* Adapt the Notification header if this view is used as an expanded view.
*
* @hide
@@ -7561,15 +7561,27 @@ public class Notification implements Parcelable
}
private int getBigPictureLayoutResource() {
- return R.layout.notification_template_material_big_picture;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_big_picture;
+ } else {
+ return R.layout.notification_template_material_big_picture;
+ }
}
private int getBigTextLayoutResource() {
- return R.layout.notification_template_material_big_text;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_big_text;
+ } else {
+ return R.layout.notification_template_material_big_text;
+ }
}
private int getInboxLayoutResource() {
- return R.layout.notification_template_material_inbox;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_inbox;
+ } else {
+ return R.layout.notification_template_material_inbox;
+ }
}
private int getCollapsedMessagingLayoutResource() {
@@ -7581,7 +7593,11 @@ public class Notification implements Parcelable
}
private int getExpandedMessagingLayoutResource() {
- return R.layout.notification_template_material_big_messaging;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_messaging;
+ } else {
+ return R.layout.notification_template_material_big_messaging;
+ }
}
private int getCollapsedMediaLayoutResource() {
@@ -7592,12 +7608,44 @@ public class Notification implements Parcelable
}
}
+ private int getExpandedMediaLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_media;
+ } else {
+ return R.layout.notification_template_material_big_media;
+ }
+ }
+
private int getConversationLayoutResource() {
- return R.layout.notification_template_material_conversation;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_conversation;
+ } else {
+ return R.layout.notification_template_material_conversation;
+ }
+ }
+
+ private int getCollapsedCallLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_collapsed_call;
+ } else {
+ return R.layout.notification_template_material_call;
+ }
+ }
+
+ private int getExpandedCallLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_call;
+ } else {
+ return R.layout.notification_template_material_big_call;
+ }
}
private int getProgressLayoutResource() {
- return R.layout.notification_template_material_progress;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_expanded_progress;
+ } else {
+ return R.layout.notification_template_material_progress;
+ }
}
private int getActionLayoutResource() {
@@ -10521,7 +10569,7 @@ public class Notification implements Parcelable
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_big_media, p , result);
+ mBuilder.getExpandedMediaLayoutResource(), p , result);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
@@ -10944,10 +10992,10 @@ public class Notification implements Parcelable
final RemoteViews contentView;
if (isCollapsed) {
contentView = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_call, p, null /* result */);
+ mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
} else {
contentView = mBuilder.applyStandardTemplateWithActions(
- R.layout.notification_template_material_big_call, p, null /* result */);
+ mBuilder.getExpandedCallLayoutResource(), p, null /* result */);
}
// Bind some extra conversation-specific header fields.
@@ -14798,12 +14846,23 @@ public class Notification implements Parcelable
} else {
mBackgroundColor = rawColor;
}
- mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
- mBackgroundColor, 4.5);
- mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
- mBackgroundColor, 4.5);
+ if (Flags.uiRichOngoing()) {
+ boolean isBgDark = Notification.Builder.isColorDark(mBackgroundColor);
+ int onSurfaceColorExtreme = isBgDark ? Color.WHITE : Color.BLACK;
+ mPrimaryTextColor = ContrastColorUtil.ensureContrast(
+ ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.9f),
+ mBackgroundColor, isBgDark, 4.5);
+ mSecondaryTextColor = ContrastColorUtil.ensureContrast(
+ ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.8f),
+ mBackgroundColor, isBgDark, 4.5);
+ } else {
+ mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
+ mBackgroundColor, 4.5);
+ mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ ContrastColorUtil.resolveSecondaryColor(ctx,
+ mBackgroundColor, nightMode), mBackgroundColor, 4.5);
+ }
mContrastColor = mPrimaryTextColor;
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index bcae22a38f9e..0348b6de9964 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -32,6 +32,12 @@ import java.util.Objects;
* {@hide}
*/
public class ProfilerInfo implements Parcelable {
+ // Regular profiling which provides different modes of profiling at some performance cost.
+ public static final int PROFILE_TYPE_REGULAR = 0;
+
+ // Low overhead profiling that captures a simple sliding window of past events.
+ public static final int PROFILE_TYPE_LOW_OVERHEAD = 1;
+
// Version of the profiler output
public static final int OUTPUT_VERSION_DEFAULT = 1;
// CLOCK_TYPE_DEFAULT chooses the default used by ART. ART uses CLOCK_TYPE_DUAL by default (see
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 3973c58c0708..1e971a5c736a 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -80,6 +80,17 @@ import java.util.concurrent.atomic.AtomicLong;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PropertyInvalidatedCache<Query, Result> {
/**
+ * A method to report if the PermissionManager notifications can be separated from cache
+ * invalidation. The feature relies on a series of flags; the dependency is captured in this
+ * method.
+ * @hide
+ */
+ public static boolean separatePermissionNotificationsEnabled() {
+ return isSharedMemoryAvailable()
+ && Flags.picSeparatePermissionNotifications();
+ }
+
+ /**
* This is a configuration class that customizes a cache instance.
* @hide
*/
@@ -1283,6 +1294,13 @@ public class PropertyInvalidatedCache<Query, Result> {
public static record Args(@NonNull String mModule, @Nullable String mApi,
int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) {
+ /**
+ * Default values for fields.
+ */
+ public static final int DEFAULT_MAX_ENTRIES = 32;
+ public static final boolean DEFAULT_ISOLATE_UIDS = true;
+ public static final boolean DEFAULT_CACHE_NULLS = false;
+
// Validation: the module must be one of the known module strings and the maxEntries must
// be positive.
public Args {
@@ -1297,10 +1315,10 @@ public class PropertyInvalidatedCache<Query, Result> {
public Args(@NonNull String module) {
this(module,
null, // api
- 32, // maxEntries
- true, // isolateUids
+ DEFAULT_MAX_ENTRIES,
+ DEFAULT_ISOLATE_UIDS,
false, // testMode
- true // allowNulls
+ DEFAULT_CACHE_NULLS
);
}
@@ -1350,7 +1368,7 @@ public class PropertyInvalidatedCache<Query, Result> {
* Burst a property name into module and api. Throw if the key is invalid. This method is
* used in to transition legacy cache constructors to the args constructor.
*/
- private static Args parseProperty(@NonNull String name) {
+ private static Args argsFromProperty(@NonNull String name) {
throwIfInvalidCacheKey(name);
// Strip off the leading well-known prefix.
String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
@@ -1373,8 +1391,9 @@ public class PropertyInvalidatedCache<Query, Result> {
*
* @hide
*/
+ @Deprecated
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
- this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
+ this(argsFromProperty(propertyName).maxEntries(maxEntries), propertyName, null);
}
/**
@@ -1388,9 +1407,10 @@ public class PropertyInvalidatedCache<Query, Result> {
* @param cacheName Name of this cache in debug and dumpsys
* @hide
*/
+ @Deprecated
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
- this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
+ this(argsFromProperty(propertyName).maxEntries(maxEntries), cacheName, null);
}
/**
@@ -1846,6 +1866,14 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
+ * Invalidate caches in all processes that have the module and api specified in the args.
+ * @hide
+ */
+ public static void invalidateCache(@NonNull Args args) {
+ invalidateCache(createPropertyName(args.mModule, args.mApi));
+ }
+
+ /**
* Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
* {@var name}. This function is synchronous: caches are invalidated upon return.
*
@@ -1921,6 +1949,10 @@ public class PropertyInvalidatedCache<Query, Result> {
}
public AutoCorker(@NonNull String propertyName, int autoCorkDelayMs) {
+ if (separatePermissionNotificationsEnabled()) {
+ throw new IllegalStateException("AutoCorking is unavailable");
+ }
+
mPropertyName = propertyName;
mAutoCorkDelayMs = autoCorkDelayMs;
// We can't initialize mHandler here: when we're created, the main loop might not
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 3cffca796680..51d0b18467f4 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1517,10 +1517,8 @@ public class ResourcesManager {
int changes = mResConfiguration.updateFrom(config);
if (compat != null && (mResCompatibilityInfo == null
|| !mResCompatibilityInfo.equals(compat))) {
+ changes |= compat.getCompatibilityChangesForConfig(mResCompatibilityInfo);
mResCompatibilityInfo = compat;
- changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
- | ActivityInfo.CONFIG_SCREEN_SIZE
- | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
// If a application info update was scheduled to occur in this process but has not
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 2bd2d34d54a2..248e0433232a 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -42,8 +42,7 @@ import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.contextualsearch.ContextualSearchManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
-import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceFrameworkInitializer;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
@@ -82,8 +81,6 @@ import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
-import android.content.integrity.AppIntegrityManager;
-import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
@@ -1581,16 +1578,6 @@ public final class SystemServiceRegistry {
return new AttestationVerificationManager(ctx.getOuterContext(),
IAttestationVerificationManagerService.Stub.asInterface(b));
}});
-
- //CHECKSTYLE:ON IndentationCheck
- registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class,
- new CachedServiceFetcher<AppIntegrityManager>() {
- @Override
- public AppIntegrityManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE);
- return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b));
- }});
registerService(Context.APP_HIBERNATION_SERVICE, AppHibernationManager.class,
new CachedServiceFetcher<AppHibernationManager>() {
@Override
@@ -1704,19 +1691,6 @@ public final class SystemServiceRegistry {
throw new ServiceNotFoundException(Context.WEARABLE_SENSING_SERVICE);
}});
- registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
- new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
- @Override
- public OnDeviceIntelligenceManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
- IBinder iBinder = ServiceManager.getServiceOrThrow(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE);
- IOnDeviceIntelligenceManager manager =
- IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
- return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
- }
- });
-
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
@@ -1861,6 +1835,7 @@ public final class SystemServiceRegistry {
ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
NearbyFrameworkInitializer.registerServiceWrappers();
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
+ OnDeviceIntelligenceFrameworkInitializer.registerServiceWrappers();
DeviceLockFrameworkInitializer.registerServiceWrappers();
VirtualizationFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5ed1f4e35533..637187e01160 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,6 +177,10 @@
{
"file_patterns": ["(/|^)AppOpsManager.java"],
"name": "CtsAppOpsTestCases"
+ },
+ {
+ "file_patterns": ["(/|^)BroadcastStickyCache.java"],
+ "name": "BroadcastUnitTests"
}
]
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 01cc9d82d56d..76705dcdd3d2 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -308,12 +308,18 @@ public class TaskInfo {
public boolean isSleeping;
/**
- * Whether the top activity fillsParent() is false
+ * Whether the top activity fillsParent() is false.
* @hide
*/
public boolean isTopActivityTransparent;
/**
+ * Whether fillsParent() is false for every activity in the tasks stack.
+ * @hide
+ */
+ public boolean isActivityStackTransparent;
+
+ /**
* The last non-fullscreen bounds the task was launched in or resized to.
* @hide
*/
@@ -489,6 +495,7 @@ public class TaskInfo {
&& parentTaskId == that.parentTaskId
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
+ && isActivityStackTransparent == that.isActivityStackTransparent
&& Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
@@ -567,6 +574,7 @@ public class TaskInfo {
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
+ isActivityStackTransparent = source.readBoolean();
lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
@@ -623,6 +631,7 @@ public class TaskInfo {
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
+ dest.writeBoolean(isActivityStackTransparent);
dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
@@ -668,6 +677,7 @@ public class TaskInfo {
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ + " isActivityStackTransparent=" + isActivityStackTransparent
+ " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index abb2dd465576..89e25e7d1b4f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2092,7 +2092,7 @@ public class WallpaperManager {
/**
* Returns the description of the designated wallpaper. Returns null if the lock screen
- * wallpaper is requested lock screen wallpaper is not set.
+ * wallpaper is requested and lock screen wallpaper is not set.
* @param which Specifies wallpaper to request (home or lock).
* @throws IllegalArgumentException if {@code which} is not exactly one of
@@ -2491,6 +2491,27 @@ public class WallpaperManager {
return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
}
+ /**
+ * Version of setBitmap that allows specification of wallpaper metadata including how the
+ * wallpaper will be positioned for different display sizes.
+ *
+ * @param fullImage A bitmap that will supply the wallpaper imagery.
+ * @param description Wallpaper metadata including desired cropping
+ * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+ * image for restore to a future device; {@code false} otherwise.
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
+ * @hide
+ */
+ @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setBitmapWithDescription(@Nullable Bitmap fullImage,
+ @NonNull WallpaperDescription description, boolean allowBackup,
+ @SetWallpaperFlags int which) throws IOException {
+ return setBitmapWithCrops(fullImage, description.getCropHints(), allowBackup, which,
+ mContext.getUserId());
+ }
+
private final void validateRect(Rect rect) {
if (rect != null && rect.isEmpty()) {
throw new IllegalArgumentException("visibleCrop rectangle must be valid and non-empty");
@@ -2700,6 +2721,27 @@ public class WallpaperManager {
}
/**
+ * Version of setStream that allows specification of wallpaper metadata including how the
+ * wallpaper will be positioned for different display sizes.
+ *
+ * @param bitmapData A stream containing the raw data to install as a wallpaper. This
+ * data can be in any format handled by {@link BitmapRegionDecoder}.
+ * @param description Wallpaper metadata including desired cropping
+ * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+ * image for restore to a future device; {@code false} otherwise.
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
+ * @hide
+ */
+ @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+ public int setStreamWithDescription(@NonNull InputStream bitmapData,
+ @NonNull WallpaperDescription description, boolean allowBackup,
+ @SetWallpaperFlags int which) throws IOException {
+ return setStreamWithCrops(bitmapData, description.getCropHints(), allowBackup, which);
+ }
+
+ /**
* Return whether any users are currently set to use the wallpaper
* with the given resource ID. That is, their wallpaper has been
* set through {@link #setResource(int)} with the same resource id.
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 1f31ab5d1849..720e045dc944 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -53,16 +53,6 @@ flag {
flag {
namespace: "backstage_power"
- name: "gate_fgs_timeout_anr_behavior"
- description: "Gate the new behavior where an ANR is thrown once an FGS times out."
- bug: "339315145"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "backstage_power"
name: "enable_fgs_timeout_crash_behavior"
description: "Enable the new behavior where the app is crashed once an FGS times out."
bug: "339526947"
@@ -164,4 +154,12 @@ flag {
name: "app_start_info_component"
description: "Control ApplicationStartInfo component field and API"
bug: "362537357"
+ is_exported: true
+}
+
+flag {
+ name: "jank_perceptible_narrow"
+ namespace: "system_performance"
+ description: "Narrow the scope of Jank Perceptible"
+ bug: "304837972"
}
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index 35149b5a3135..89261a4f85ee 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.appfunctions.flags.Flags;
import android.os.UserManager;
import java.util.Objects;
@@ -181,6 +182,12 @@ public final class DevicePolicyIdentifiers {
public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
/**
+ * String identifier for {@link DevicePolicyManager#setAppFunctionsPolicy(int)}.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final String APP_FUNCTIONS_POLICY = "appFunctions";
+
+ /**
* String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
*/
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 74d729857cc8..9ddc729850c4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.LOCK_DEVICE;
import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
@@ -3931,6 +3932,11 @@ public class DevicePolicyManager {
@FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41;
+ /** @hide */
+ @TestApi
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final int OPERATION_SET_APP_FUNCTIONS_POLICY = 42;
+
private static final String PREFIX_OPERATION = "OPERATION_";
/** @hide */
@@ -3975,7 +3981,8 @@ public class DevicePolicyManager {
OPERATION_SET_PERMISSION_POLICY,
OPERATION_SET_RESTRICTIONS_PROVIDER,
OPERATION_UNINSTALL_CA_CERT,
- OPERATION_SET_CONTENT_PROTECTION_POLICY
+ OPERATION_SET_CONTENT_PROTECTION_POLICY,
+ OPERATION_SET_APP_FUNCTIONS_POLICY
})
@Retention(RetentionPolicy.SOURCE)
public static @interface DevicePolicyOperation {
@@ -4347,6 +4354,90 @@ public class DevicePolicyManager {
}
/**
+ * Indicates that app functions are not controlled by policy.
+ *
+ * <p>If no admin set this policy, it means appfunctions are enabled.
+ */
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final int APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY = 0;
+
+ /** Indicates that app functions are controlled and disabled by a policy. */
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final int APP_FUNCTIONS_DISABLED = 1;
+
+ /**
+ * Indicates that app functions are controlled and disabled by a policy for cross profile
+ * interactions only.
+ *
+ * <p>This is different from {@link #APP_FUNCTIONS_DISABLED} in that it only disables cross
+ * profile interactions (even if the caller has permissions required to interact across users).
+ * appfunctions can still be used within the a user profile boundary.
+ */
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final int APP_FUNCTIONS_DISABLED_CROSS_PROFILE = 2;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"APP_FUNCTIONS_"},
+ value = {
+ APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY,
+ APP_FUNCTIONS_DISABLED,
+ APP_FUNCTIONS_DISABLED_CROSS_PROFILE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AppFunctionsPolicy {}
+
+ /**
+ * Sets the app functions policy which controls app functions operations on the device.
+ *
+ * <p>This function can only be called by a device owner, a profile owner or holders of the
+ * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_FUNCTIONS}.
+ *
+ * @param policy The app functions policy to set. One of {@link
+ * #APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY},
+ * {@link #APP_FUNCTIONS_DISABLED} or
+ * {@link #APP_FUNCTIONS_DISABLED_CROSS_PROFILE}
+ * @throws SecurityException if caller is not a device owner, a profile owner or a holder
+ * of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_FUNCTIONS}.
+ */
+ @RequiresPermission(value = MANAGE_DEVICE_POLICY_APP_FUNCTIONS, conditional = true)
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public void setAppFunctionsPolicy(@AppFunctionsPolicy int policy) {
+ throwIfParentInstance("setAppFunctionsPolicy");
+ if (mService != null) {
+ try {
+ mService.setAppFunctionsPolicy(mContext.getPackageName(), policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the current app functions policy.
+ *
+ * <p>The returned policy will be the current resolved policy rather than the policy set by the
+ * calling admin.
+ *
+ * @throws SecurityException if caller is not a device owner, a profile owner or a holder
+ * of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_FUNCTIONS}.
+ */
+ @RequiresPermission(value = MANAGE_DEVICE_POLICY_APP_FUNCTIONS, conditional = true)
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public @AppFunctionsPolicy int getAppFunctionsPolicy() {
+ throwIfParentInstance("getAppFunctionsPolicy");
+ if (mService != null) {
+ try {
+ return mService.getAppFunctionsPolicy(mContext.getPackageName(),
+ myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ /**
* This object is a single place to tack on invalidation and disable calls. All
* binder caches in this class derive from this Config, so all can be invalidated or
* disabled through this Config.
@@ -12486,6 +12577,36 @@ public class DevicePolicyManager {
}
/**
+ * Returns the {@link EnforcingAdmin} who have set this policy.
+ *
+ * <p>Important: this API is a temporary solution, hence should be kept hidden. That is because
+ * the string argument can't define policies with arguments.
+ *
+ * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
+ * behavior the same as before the bug fix for b/192245204.
+ *
+ * <p>This API is only callable by the system UID
+ *
+ * @param userId The user for whom to retrieve the information.
+ * @param identifier The policy enforced by admins. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. This also works
+ * for {@link DevicePolicyIdentifiers#MEMORY_TAGGING_POLICY}.
+ *
+ * @hide
+ */
+ public @Nullable EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
+ if (mService != null) {
+ try {
+ return mService.getEnforcingAdmin(userId, identifier);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns the list of {@link EnforcingAdmin}s who have set this restriction.
*
* <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e7be822d52d3..f304c1bf0a88 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -279,8 +279,9 @@ interface IDevicePolicyManager {
boolean isNotificationListenerServicePermitted(in String packageName, int userId);
Intent createAdminSupportIntent(in String restriction);
- Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
- List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,String restriction);
+ Bundle getEnforcingAdminAndUserDetails(int userId, String restriction);
+ EnforcingAdmin getEnforcingAdmin(int userId, String identifier);
+ List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId, String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
@@ -643,4 +644,7 @@ interface IDevicePolicyManager {
int getPolicySizeForAdmin(String callerPackageName, in EnforcingAdmin admin);
int getHeadlessDeviceOwnerMode(String callerPackageName);
+
+ void setAppFunctionsPolicy(String callerPackageName, int policy);
+ int getAppFunctionsPolicy(String callerPackageName, int userId);
}
diff --git a/core/java/android/app/admin/UnknownAuthority.java b/core/java/android/app/admin/UnknownAuthority.java
index fdad898b7bd9..bebffdea5f02 100644
--- a/core/java/android/app/admin/UnknownAuthority.java
+++ b/core/java/android/app/admin/UnknownAuthority.java
@@ -22,6 +22,8 @@ import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
+import java.util.Objects;
+
/**
* Class used to identify a default value for the authority of the {@link EnforcingAdmin} setting
* a policy, meaning it is not one of the other known subclasses of {@link Authority}, this would be
@@ -31,6 +33,7 @@ import android.os.Parcel;
*/
@SystemApi
public final class UnknownAuthority extends Authority {
+ private final String mName;
/**
* Object representing an unknown authority.
@@ -45,22 +48,40 @@ public final class UnknownAuthority extends Authority {
* Creates an authority that represents an admin that can set a policy but
* doesn't have a known authority (e.g. a system components).
*/
- public UnknownAuthority() {}
+ public UnknownAuthority() {
+ mName = null;
+ }
+
+ /** @hide */
+ public UnknownAuthority(String name) {
+ mName = name;
+ }
+
+ private UnknownAuthority(Parcel source) {
+ this(source.readString8());
+ }
+
+ /** @hide */
+ public String getName() {
+ return mName;
+ }
@Override
public String toString() {
- return "DefaultAuthority {}";
+ return "DefaultAuthority {" + mName + "}";
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
- return o != null && getClass() == o.getClass();
+ if (o == null || getClass() != o.getClass()) return false;
+ UnknownAuthority other = (UnknownAuthority) o;
+ return Objects.equals(mName, other.mName);
}
@Override
public int hashCode() {
- return 0;
+ return Objects.hashCode(mName);
}
@Override
@@ -69,14 +90,16 @@ public final class UnknownAuthority extends Authority {
}
@Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {}
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mName);
+ }
@NonNull
public static final Creator<UnknownAuthority> CREATOR =
new Creator<UnknownAuthority>() {
@Override
public UnknownAuthority createFromParcel(Parcel source) {
- return UNKNOWN_AUTHORITY;
+ return new UnknownAuthority(source);
}
@Override
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 581efa5d2efa..af035cb630dc 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -286,6 +286,16 @@ flag {
}
flag {
+ name: "unsuspend_not_suspended"
+ namespace: "enterprise"
+ description: "When admin unsuspends packages, pass previously not suspended packages to PM too"
+ bug: "378766314"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "backup_connected_apps_settings"
namespace: "enterprise"
description: "backup and restore connected work and personal apps user settings across devices"
@@ -333,16 +343,6 @@ flag {
}
flag {
- name: "dont_write_policy_definition"
- namespace: "enterprise"
- description: "Don't write redundant policy-definition-entry tags"
- bug: "335663055"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "active_admin_cleanup"
namespace: "enterprise"
description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
@@ -367,6 +367,7 @@ flag {
namespace: "enterprise"
description: "API that removes a given managed profile."
bug: "372652841"
+ is_exported: true
}
flag {
@@ -387,9 +388,11 @@ flag {
flag {
name: "split_create_managed_profile_enabled"
+ is_exported: true
namespace: "enterprise"
description: "Split up existing create and provision managed profile API."
bug: "375382324"
+ is_exported: true
}
flag {
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
index cbd1d932ab00..d8179c7540d9 100644
--- a/core/java/android/app/appfunctions/AppFunctionException.java
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -29,7 +29,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-/** Represents an app function related errors. */
+/**
+ * Represents an app function related error.
+ *
+ * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle} containing
+ * additional error-specific metadata.
+ *
+ * <p>The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle.
+ */
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
public final class AppFunctionException extends Exception implements Parcelable {
/**
@@ -78,6 +85,13 @@ public final class AppFunctionException extends Exception implements Parcelable
public static final int ERROR_CANCELLED = 2001;
/**
+ * The operation was disallowed by enterprise policy.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002;
+
+ /**
* An unknown error occurred while processing the call in the AppFunctionService.
*
* <p>This error is thrown when the service is connected in the remote application but an
@@ -224,7 +238,8 @@ public final class AppFunctionException extends Exception implements Parcelable
ERROR_SYSTEM_ERROR,
ERROR_INVALID_ARGUMENT,
ERROR_DISABLED,
- ERROR_CANCELLED
+ ERROR_CANCELLED,
+ ERROR_ENTERPRISE_POLICY_DISALLOWED
})
@Retention(RetentionPolicy.SOURCE)
public @interface ErrorCode {}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 1557815a8468..bdc6ce5c73e8 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -27,7 +27,16 @@ import android.os.Parcelable;
import java.util.Objects;
-/** A request to execute an app function. */
+/**
+ * A request to execute an app function.
+ *
+ * <p>The {@link ExecuteAppFunctionRequest#getParameters()} contains the parameters for the function
+ * to be executed in a GenericDocument. Structured classes defined in the AppFunction SDK can be
+ * converted into GenericDocuments.
+ *
+ * <p>The {@link ExecuteAppFunctionRequest#getExtras()} provides any extra metadata for the request.
+ * Structured APIs can be exposed in the SDK by packing and unpacking this Bundle.
+ */
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
public final class ExecuteAppFunctionRequest implements Parcelable {
@NonNull
@@ -127,6 +136,16 @@ public final class ExecuteAppFunctionRequest implements Parcelable {
return mExtras;
}
+ /**
+ * Returns the size of the request in bytes.
+ *
+ * @hide
+ */
+ public int getRequestDataSize() {
+ return mTargetPackageName.getBytes().length + mFunctionIdentifier.getBytes().length
+ + mParameters.getDataSize() + mExtras.getSize();
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mTargetPackageName);
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index acad43b782e5..618cc1ca48f9 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -27,7 +27,16 @@ import android.os.Parcelable;
import java.util.Objects;
-/** The response to an app function execution. */
+/**
+ * The response to an app function execution.
+ *
+ * <p>The {@link ExecuteAppFunctionResponse#getResultDocument()} contains the function's return
+ * value as a GenericDocument. This can be converted back into a structured class using the
+ * AppFunction SDK.
+ *
+ * <p>The {@link ExecuteAppFunctionResponse#getExtras()} provides any extra metadata returned by the
+ * function. The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle.
+ */
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
public final class ExecuteAppFunctionResponse implements Parcelable {
@NonNull
@@ -126,6 +135,15 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
return mExtras;
}
+ /**
+ * Returns the size of the response in bytes.
+ *
+ * @hide
+ */
+ public int getResponseDataSize() {
+ return mResultDocumentWrapper.getDataSize() + mExtras.getSize();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index 541ca7458efe..02133b475ec2 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -50,6 +50,10 @@ public final class GenericDocumentWrapper implements Parcelable {
@Nullable
private Parcel mParcel;
+ @GuardedBy("mLock")
+ @Nullable
+ private Integer mDataSize;
+
private final Object mLock = new Object();
public static final Creator<GenericDocumentWrapper> CREATOR =
@@ -75,11 +79,13 @@ public final class GenericDocumentWrapper implements Parcelable {
public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
mGenericDocument = Objects.requireNonNull(genericDocument);
mParcel = null;
+ mDataSize = null;
}
public GenericDocumentWrapper(@NonNull Parcel parcel) {
mGenericDocument = null;
mParcel = Objects.requireNonNull(parcel);
+ mDataSize = mParcel.dataSize();
}
/** Returns the wrapped {@link android.app.appsearch.GenericDocument} */
@@ -109,6 +115,21 @@ public final class GenericDocumentWrapper implements Parcelable {
}
}
+ /** Returns the size of the parcelled document. */
+
+ int getDataSize() {
+ synchronized (mLock) {
+ if (mDataSize != null) {
+ return mDataSize;
+ }
+ Parcel tempParcel = Parcel.obtain();
+ writeToParcel(tempParcel, 0);
+ mDataSize = tempParcel.dataSize();
+ tempParcel.recycle();
+ return mDataSize;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index 43a46ba7885d..3e3ca2488bd3 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -30,6 +30,31 @@ public class AssistContent implements Parcelable {
public static final String EXTRA_APP_FUNCTION_DATA =
"android.app.assist.extra.APP_FUNCTION_DATA";
+ /**
+ * This extra can be optionally supplied in the {@link #getExtras} bundle to provide a
+ * {@link Uri} which will be utilized when transitioning a user's session to another surface.
+ *
+ * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the
+ * "Open in browser" feature will use this URI to transition the current session from one
+ * surface to the other. Apps may choose to encode session or user information into this
+ * URI in order to provide a better session transfer experience.
+ *
+ * <p>Unlike {@link #setWebUri}, this URI will not be used for features where the user might
+ * accidentally share it with another user. However, developers should not encode
+ * authentication credentials into this URI, because it will be surfaced in the browser URL
+ * bar and may be copied and shared from there.
+ *
+ * <p>When providing this extra, developers should still continue to provide
+ * {@link #setWebUri} for backwards compatibility with features such as
+ * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing">
+ * recents URL sharing</a> which do not benefit from a session-transfer web URI.
+ *
+ * @see android.app.Activity#requestOpenInBrowserEducation()
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+ public static final String EXTRA_SESSION_TRANSFER_WEB_URI =
+ "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mIsAppProvidedIntent = false;
private boolean mIsAppProvidedWebUri = false;
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 5e09517d1edc..e8cfd79c9cc7 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "machine_learning"
description: "Flag to enable the service"
bug: "309689654"
+ is_exported: true
}
flag {
name: "enable_token_refresh"
@@ -16,14 +17,14 @@ flag {
flag {
name: "multi_window_screen_context"
- namespace: "machine_learning"
+ namespace: "sysui_integrations"
description: "Report screen context and positions for all windows."
bug: "371065456"
}
flag {
name: "contextual_search_window_layer"
- namespace: "machine_learning"
+ namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
bug: "372510690"
} \ No newline at end of file
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index 5657f7ee5194..a62df1b3cbf7 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "system_performance"
description: "Control the API portion of Detailed Application Jank Metrics"
bug: "366264614"
+ is_exported: true
}
flag {
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8b6840c1b552..7543fa9f581f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -284,6 +284,13 @@ flag {
}
flag {
+ name: "nm_binder_perf_cache_channels"
+ namespace: "systemui"
+ description: "Use IpcDataCache for notification channel/group lookups"
+ bug: "362981561"
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS
deleted file mode 100644
index 85e9e653e6fb..000000000000
--- a/core/java/android/app/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# Bug component: 1363385
-
-sandeepbandaru@google.com
-shivanker@google.com
-hackz@google.com
-volnov@google.com
diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
deleted file mode 100644
index 74a96c864167..000000000000
--- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig
+++ /dev/null
@@ -1,17 +0,0 @@
-package: "android.app.ondeviceintelligence.flags"
-container: "system"
-
-flag {
- name: "enable_on_device_intelligence"
- is_exported: true
- namespace: "ondeviceintelligence"
- description: "Make methods on OnDeviceIntelligenceManager available for local inference."
- bug: "304755128"
-}
-flag {
- name: "enable_on_device_intelligence_module"
- is_exported: true
- namespace: "ondeviceintelligence"
- description: "Enable migration to mainline module and related changes."
- bug: "376427781"
-} \ No newline at end of file
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 359c84eeb559..238f1cb12208 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -37,6 +37,14 @@ flag {
flag {
namespace: "system_performance"
+ name: "pic_separate_permission_notifications"
+ is_fixed_read_only: true
+ description: "Seperate PermissionManager notifications from cache udpates"
+ bug: "379699402"
+}
+
+flag {
+ namespace: "system_performance"
name: "pic_cache_nulls"
is_fixed_read_only: true
description: "Cache null returns from binder calls"
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 2b52681d1be8..75ecabd8ddb0 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -56,7 +56,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
+ CompatibilityInfo.applyOverrideIfNeeded(mConfiguration);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
client.updatePendingActivityConfiguration(getActivityToken(), mConfiguration);
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index cecf7013c79c..bb881908d10f 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -89,7 +89,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
public void preExecute(@NonNull ClientTransactionHandler client) {
// The local config is already scaled so only apply if this item is from server side.
if (!client.isExecutingLocalTransaction()) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
+ CompatibilityInfo.applyOverrideIfNeeded(mConfig);
}
mActivityClientRecord = client.prepareRelaunchActivity(getActivityToken(), mPendingResults,
mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow, mActivityWindowInfo);
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 123d7926160c..e42005bdd595 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -46,7 +46,7 @@ public class ConfigurationChangeItem extends ClientTransactionItem {
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
+ CompatibilityInfo.applyOverrideIfNeeded(mConfiguration);
client.updatePendingConfiguration(mConfiguration);
}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 235a9f7aeb4c..f2e7a4fcd50b 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -202,8 +202,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
public void preExecute(@NonNull ClientTransactionHandler client) {
client.countLaunchingActivities(1);
client.updateProcessState(mProcState, false);
- CompatibilityInfo.applyOverrideScaleIfNeeded(mCurConfig);
- CompatibilityInfo.applyOverrideScaleIfNeeded(mOverrideConfig);
+ CompatibilityInfo.applyOverrideIfNeeded(mCurConfig);
+ CompatibilityInfo.applyOverrideIfNeeded(mOverrideConfig);
client.updatePendingConfiguration(mCurConfig);
if (mActivityClientController != null) {
ActivityClient.setActivityClientController(mActivityClientController);
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 1aa563aa6363..72d1f491f2c6 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -58,7 +58,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
+ CompatibilityInfo.applyOverrideIfNeeded(mConfiguration);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
client.updatePendingActivityConfiguration(getActivityToken(), mConfiguration);
diff --git a/core/java/android/app/supervision/SupervisionManagerInternal.java b/core/java/android/app/supervision/SupervisionManagerInternal.java
index d571e14ff5fa..2cf6ae6f9d01 100644
--- a/core/java/android/app/supervision/SupervisionManagerInternal.java
+++ b/core/java/android/app/supervision/SupervisionManagerInternal.java
@@ -27,32 +27,41 @@ import android.os.PersistableBundle;
*/
public abstract class SupervisionManagerInternal {
/**
- * Returns whether supervision is enabled for the specified user
+ * Returns whether the app with given process uid is the active supervision app.
*
- * @param userId The user to retrieve the supervision state for
- * @return whether the user is supervised
+ * <p>Supervision app is considered active when supervision is enabled for the user running the
+ * given process uid.
+ *
+ * @param uid App process uid.
+ * @return Whether the app is the active supervision app.
*/
- public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
+ public abstract boolean isActiveSupervisionApp(int uid);
/**
- * Returns whether the supervision lock screen needs to be shown.
+ * Returns whether supervision is enabled for the specified user.
+ *
+ * @param userId The user to retrieve the supervision state for.
+ * @return Whether the user is supervised.
*/
+ public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
+
+ /** Returns whether the supervision lock screen needs to be shown. */
public abstract boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId);
/**
* Set whether supervision is enabled for the specified user.
*
- * @param userId The user to set the supervision state for
- * @param enabled Whether or not the user should be supervised
+ * @param userId The user to set the supervision state for.
+ * @param enabled Whether or not the user should be supervised.
*/
public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled);
/**
- * Sets whether the supervision lock screen should be shown for the specified user
+ * Sets whether the supervision lock screen should be shown for the specified user.
*
- * @param userId The user set the superivision state for
- * @param enabled Whether or not the superivision lock screen needs to be shown
- * @param options Optional configuration parameters for the supervision lock screen
+ * @param userId The user set the superivision state for.
+ * @param enabled Whether or not the superivision lock screen needs to be shown.
+ * @param options Optional configuration parameters for the supervision lock screen.
*/
public abstract void setSupervisionLockscreenEnabledForUser(
@UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options);
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index d5e696d49ff4..1b0353274fb9 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -16,3 +16,19 @@ flag {
description: "Flag to enable the SupervisionService on Wear devices"
bug: "373358935"
}
+
+flag {
+ name: "enable_sync_with_dpm"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables supervision when the supervision app is the profile owner"
+ bug: "377261590"
+}
+
+flag {
+ name: "deprecate_dpm_supervision_apis"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that deprecates supervision methods in DPM"
+ bug: "382034839"
+}
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index 4a142bb5287a..ca2d9e676a02 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -19,8 +19,15 @@ package android.app.wallpaper;
import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
import android.annotation.FlaggedApi;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.app.WallpaperManager.ScreenOrientation;
import android.content.ComponentName;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,6 +36,8 @@ import android.text.Html;
import android.text.Spanned;
import android.text.SpannedString;
import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -43,6 +52,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -71,12 +81,15 @@ public final class WallpaperDescription implements Parcelable {
@Nullable private final Uri mContextUri;
@Nullable private final CharSequence mContextDescription;
@NonNull private final PersistableBundle mContent;
+ @NonNull private final SparseArray<Rect> mCropHints;
+ private final float mSampleSize;
private WallpaperDescription(@Nullable ComponentName component,
@Nullable String id, @Nullable Uri thumbnail, @Nullable CharSequence title,
@Nullable List<CharSequence> description, @Nullable Uri contextUri,
@Nullable CharSequence contextDescription,
- @Nullable PersistableBundle content) {
+ @Nullable PersistableBundle content, @NonNull SparseArray<Rect> cropHints,
+ float sampleSize) {
this.mComponent = component;
this.mId = id;
this.mThumbnail = thumbnail;
@@ -85,6 +98,8 @@ public final class WallpaperDescription implements Parcelable {
this.mContextUri = contextUri;
this.mContextDescription = contextDescription;
this.mContent = (content != null) ? content : new PersistableBundle();
+ this.mCropHints = cropHints;
+ this.mSampleSize = sampleSize;
}
/** @return the component for this wallpaper, or {@code null} for a static wallpaper */
@@ -134,6 +149,25 @@ public final class WallpaperDescription implements Parcelable {
return mContent;
}
+ /**
+ * @return the cropping for the current image as described in
+ * {@link Builder#setCropHints(SparseArray)}
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public SparseArray<Rect> getCropHints() {
+ return mCropHints;
+ }
+
+ /**
+ * @return the subsamling size as described in {@link Builder#setSampleSize(float)}.
+ * @hide
+ */
+ public float getSampleSize() {
+ return mSampleSize;
+ }
+
////// Comparison overrides
@Override
@@ -163,9 +197,23 @@ public final class WallpaperDescription implements Parcelable {
if (mContextDescription != null) {
out.attribute(null, "contextdescription", toHtml(mContextDescription));
}
+
+ for (Pair<Integer, String> pair : screenDimensionPairs()) {
+ @ScreenOrientation int orientation = pair.first;
+ String attrName = pair.second;
+ Rect cropHint = mCropHints.get(orientation);
+ if (cropHint == null) continue;
+ out.attributeInt(null, "cropLeft" + attrName, cropHint.left);
+ out.attributeInt(null, "cropTop" + attrName, cropHint.top);
+ out.attributeInt(null, "cropRight" + attrName, cropHint.right);
+ out.attributeInt(null, "cropBottom" + attrName, cropHint.bottom);
+ }
+ out.attributeFloat(null, "sampleSize", mSampleSize);
+
out.startTag(null, XML_TAG_DESCRIPTION);
for (CharSequence s : mDescription) out.attribute(null, "descriptionline", toHtml(s));
out.endTag(null, XML_TAG_DESCRIPTION);
+
try {
out.startTag(null, XML_TAG_CONTENT);
mContent.saveToXml(out);
@@ -194,6 +242,19 @@ public final class WallpaperDescription implements Parcelable {
CharSequence contextDescription = fromHtml(
in.getAttributeValue(null, "contextdescription"));
+ SparseArray<Rect> cropHints = new SparseArray<>();
+ screenDimensionPairs().forEach(pair -> {
+ @ScreenOrientation int orientation = pair.first;
+ String attrName = pair.second;
+ Rect crop = new Rect(
+ in.getAttributeInt(null, "cropLeft" + attrName, 0),
+ in.getAttributeInt(null, "cropTop" + attrName, 0),
+ in.getAttributeInt(null, "cropRight" + attrName, 0),
+ in.getAttributeInt(null, "cropBottom" + attrName, 0));
+ if (!crop.isEmpty()) cropHints.put(orientation, crop);
+ });
+ float sampleSize = in.getAttributeFloat(null, "sampleSize", 1f);
+
List<CharSequence> description = new ArrayList<>();
PersistableBundle content = null;
int type;
@@ -213,7 +274,7 @@ public final class WallpaperDescription implements Parcelable {
}
return new WallpaperDescription(componentName, id, thumbnail, title, description,
- contextUri, contextDescription, content);
+ contextUri, contextDescription, content, cropHints, sampleSize);
}
private static String toHtml(@NonNull CharSequence c) {
@@ -253,6 +314,13 @@ public final class WallpaperDescription implements Parcelable {
mContextUri = Uri.CREATOR.createFromParcel(in);
mContextDescription = in.readCharSequence();
mContent = PersistableBundle.CREATOR.createFromParcel(in);
+ mCropHints = new SparseArray<>();
+ screenDimensionPairs().forEach(pair -> {
+ int orientation = pair.first;
+ Rect crop = in.readTypedObject(Rect.CREATOR);
+ if (crop != null) mCropHints.put(orientation, crop);
+ });
+ mSampleSize = in.readFloat();
}
@NonNull
@@ -283,6 +351,11 @@ public final class WallpaperDescription implements Parcelable {
Uri.writeToParcel(dest, mContextUri);
dest.writeCharSequence(mContextDescription);
dest.writePersistableBundle(mContent);
+ screenDimensionPairs().forEach(pair -> {
+ int orientation = pair.first;
+ dest.writeTypedObject(mCropHints.get(orientation), flags);
+ });
+ dest.writeFloat(mSampleSize);
}
////// Builder
@@ -293,9 +366,17 @@ public final class WallpaperDescription implements Parcelable {
*/
@NonNull
public Builder toBuilder() {
- return new Builder().setComponent(mComponent).setId(mId).setThumbnail(mThumbnail).setTitle(
- mTitle).setDescription(mDescription).setContextUri(
- mContextUri).setContextDescription(mContextDescription).setContent(mContent);
+ return new Builder()
+ .setComponent(mComponent)
+ .setId(mId)
+ .setThumbnail(mThumbnail)
+ .setTitle(mTitle)
+ .setDescription(mDescription)
+ .setContextUri(mContextUri)
+ .setContextDescription(mContextDescription)
+ .setContent(mContent)
+ .setCropHints(mCropHints)
+ .setSampleSize(mSampleSize);
}
/** Builder for the immutable {@link WallpaperDescription} class */
@@ -308,6 +389,9 @@ public final class WallpaperDescription implements Parcelable {
@Nullable private Uri mContextUri;
@Nullable private CharSequence mContextDescription;
@NonNull private PersistableBundle mContent = new PersistableBundle();
+ @NonNull
+ private SparseArray<Rect> mCropHints = new SparseArray<>();
+ private float mSampleSize = 1f;
/** Creates a new, empty {@link Builder}. */
public Builder() {}
@@ -416,11 +500,69 @@ public final class WallpaperDescription implements Parcelable {
return this;
}
+ /**
+ * Defines which part of the source wallpaper image is in the stored crop file.
+ *
+ * @param cropHints map from screen dimensions to a sub-region of the image to display
+ * for those dimensions. The {@code Rect} sub-region may have a larger
+ * width/height ratio than the screen dimensions to apply a horizontal
+ * parallax effect. If the map is empty or some entries are missing, the
+ * system will apply a default strategy to position the wallpaper for
+ * any unspecified screen dimensions.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setCropHints(@NonNull Map<Point, Rect> cropHints) {
+ mCropHints = new SparseArray<>();
+ cropHints.forEach(
+ (point, rect) -> mCropHints.put(WallpaperManager.getOrientation(point), rect));
+ return this;
+ }
+
+ /**
+ * Defines which part of the source wallpaper image is in the stored crop file.
+ *
+ * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to
+ * display for that screen orientation.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setCropHints(@NonNull SparseArray<Rect> cropHints) {
+ mCropHints = cropHints;
+ return this;
+ }
+
+ /**
+ * How much the crop is sub-sampled. A value > 1 means that the image quality was reduced.
+ * This is the ratio between the cropHint height and the actual stored crop file height.
+ * height.
+ *
+ * @param sampleSize Sub-sampling value
+ * @hide
+ */
+ @NonNull
+ public Builder setSampleSize(float sampleSize) {
+ mSampleSize = sampleSize;
+ return this;
+ }
+
/** Creates and returns the {@link WallpaperDescription} represented by this builder. */
@NonNull
public WallpaperDescription build() {
return new WallpaperDescription(mComponent, mId, mThumbnail, mTitle, mDescription,
- mContextUri, mContextDescription, mContent);
+ mContextUri, mContextDescription, mContent, mCropHints, mSampleSize);
}
}
+
+ private static List<Pair<Integer, String>> screenDimensionPairs() {
+ return List.of(
+ new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+ new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+ new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+ new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, "SquareLandscape"));
+ }
}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 17bcdb013673..9914ba2b020a 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -111,4 +111,5 @@ flag {
namespace: "app_widgets"
description: "Enable collection of widget engagement metrics"
bug: "364655296"
+ is_exported: true
}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 124973489dd1..2f161150a89b 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -54,8 +54,6 @@ public final class AssociationInfo implements Parcelable {
@NonNull
private final String mPackageName;
@Nullable
- private final String mTag;
- @Nullable
private final MacAddress mDeviceMacAddress;
@Nullable
private final CharSequence mDisplayName;
@@ -85,6 +83,8 @@ public final class AssociationInfo implements Parcelable {
*/
private final long mLastTimeConnectedMs;
private final int mSystemDataSyncFlags;
+ @Nullable
+ private final DeviceId mDeviceId;
/**
* A device icon displayed on a selfManaged association dialog.
@@ -97,11 +97,11 @@ public final class AssociationInfo implements Parcelable {
* @hide
*/
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
- @Nullable String tag, @Nullable MacAddress macAddress,
- @Nullable CharSequence displayName, @Nullable String deviceProfile,
- @Nullable AssociatedDevice associatedDevice, boolean selfManaged,
- boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
- long lastTimeConnectedMs, int systemDataSyncFlags, @Nullable Icon deviceIcon) {
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+ boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, boolean pending,
+ long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags,
+ @Nullable Icon deviceIcon, @Nullable DeviceId deviceId) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -115,7 +115,6 @@ public final class AssociationInfo implements Parcelable {
mPackageName = packageName;
mDeviceMacAddress = macAddress;
mDisplayName = displayName;
- mTag = tag;
mDeviceProfile = deviceProfile;
mAssociatedDevice = associatedDevice;
mSelfManaged = selfManaged;
@@ -126,6 +125,7 @@ public final class AssociationInfo implements Parcelable {
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
mDeviceIcon = deviceIcon;
+ mDeviceId = deviceId;
}
/**
@@ -155,13 +155,13 @@ public final class AssociationInfo implements Parcelable {
}
/**
- * @return the tag of this association.
- * @see CompanionDeviceManager#setAssociationTag(int, String)
+ * @return the {@link DeviceId} of this association.
+ * @see CompanionDeviceManager#setDeviceId(int, DeviceId)
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@Nullable
- public String getTag() {
- return mTag;
+ public DeviceId getDeviceId() {
+ return mDeviceId;
}
/**
@@ -355,7 +355,6 @@ public final class AssociationInfo implements Parcelable {
+ "mId=" + mId
+ ", mUserId=" + mUserId
+ ", mPackageName='" + mPackageName + '\''
- + ", mTag='" + mTag + '\''
+ ", mDeviceMacAddress=" + mDeviceMacAddress
+ ", mDisplayName='" + mDisplayName + '\''
+ ", mDeviceProfile='" + mDeviceProfile + '\''
@@ -369,6 +368,7 @@ public final class AssociationInfo implements Parcelable {
mLastTimeConnectedMs == Long.MAX_VALUE
? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs))
+ ", mSystemDataSyncFlags=" + mSystemDataSyncFlags
+ + ", mDeviceId='" + mDeviceId
+ '}';
}
@@ -386,21 +386,22 @@ public final class AssociationInfo implements Parcelable {
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
- && Objects.equals(mTag, that.mTag)
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mDisplayName, that.mDisplayName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSystemDataSyncFlags == that.mSystemDataSyncFlags
&& (mDeviceIcon == null ? that.mDeviceIcon == null
- : mDeviceIcon.sameAs(that.mDeviceIcon));
+ : mDeviceIcon.sameAs(that.mDeviceIcon))
+ && Objects.equals(mDeviceId, that.mDeviceId);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
+ return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon,
+ mDeviceId);
}
@Override
@@ -413,7 +414,6 @@ public final class AssociationInfo implements Parcelable {
dest.writeInt(mId);
dest.writeInt(mUserId);
dest.writeString(mPackageName);
- dest.writeString(mTag);
dest.writeTypedObject(mDeviceMacAddress, 0);
dest.writeCharSequence(mDisplayName);
dest.writeString(mDeviceProfile);
@@ -431,13 +431,19 @@ public final class AssociationInfo implements Parcelable {
} else {
dest.writeInt(0);
}
+
+ if (Flags.associationTag() && mDeviceId != null) {
+ dest.writeInt(1);
+ dest.writeTypedObject(mDeviceId, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
private AssociationInfo(@NonNull Parcel in) {
mId = in.readInt();
mUserId = in.readInt();
mPackageName = in.readString();
- mTag = in.readString();
mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
mDisplayName = in.readCharSequence();
mDeviceProfile = in.readString();
@@ -454,6 +460,12 @@ public final class AssociationInfo implements Parcelable {
} else {
mDeviceIcon = null;
}
+ int deviceId = in.readInt();
+ if (Flags.associationTag() && deviceId == 1) {
+ mDeviceId = in.readTypedObject(DeviceId.CREATOR);
+ } else {
+ mDeviceId = null;
+ }
}
@NonNull
@@ -481,7 +493,6 @@ public final class AssociationInfo implements Parcelable {
private final int mId;
private final int mUserId;
private final String mPackageName;
- private String mTag;
private MacAddress mDeviceMacAddress;
private CharSequence mDisplayName;
private String mDeviceProfile;
@@ -494,6 +505,7 @@ public final class AssociationInfo implements Parcelable {
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
private Icon mDeviceIcon;
+ private DeviceId mDeviceId;
/** @hide */
@TestApi
@@ -509,7 +521,6 @@ public final class AssociationInfo implements Parcelable {
mId = info.mId;
mUserId = info.mUserId;
mPackageName = info.mPackageName;
- mTag = info.mTag;
mDeviceMacAddress = info.mDeviceMacAddress;
mDisplayName = info.mDisplayName;
mDeviceProfile = info.mDeviceProfile;
@@ -522,6 +533,7 @@ public final class AssociationInfo implements Parcelable {
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
mDeviceIcon = info.mDeviceIcon;
+ mDeviceId = info.mDeviceId;
}
/**
@@ -534,7 +546,6 @@ public final class AssociationInfo implements Parcelable {
mId = id;
mUserId = userId;
mPackageName = packageName;
- mTag = info.mTag;
mDeviceMacAddress = info.mDeviceMacAddress;
mDisplayName = info.mDisplayName;
mDeviceProfile = info.mDeviceProfile;
@@ -547,14 +558,15 @@ public final class AssociationInfo implements Parcelable {
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
mDeviceIcon = info.mDeviceIcon;
+ mDeviceId = info.mDeviceId;
}
/** @hide */
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@TestApi
@NonNull
- public Builder setTag(@Nullable String tag) {
- mTag = tag;
+ public Builder setDeviceId(@Nullable DeviceId deviceId) {
+ mDeviceId = deviceId;
return this;
}
@@ -684,7 +696,6 @@ public final class AssociationInfo implements Parcelable {
mId,
mUserId,
mPackageName,
- mTag,
mDeviceMacAddress,
mDisplayName,
mDeviceProfile,
@@ -696,7 +707,8 @@ public final class AssociationInfo implements Parcelable {
mTimeApprovedMs,
mLastTimeConnectedMs,
mSystemDataSyncFlags,
- mDeviceIcon
+ mDeviceIcon,
+ mDeviceId
);
}
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4472c3d13d7c..a96ba11eb482 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -278,12 +278,6 @@ public final class CompanionDeviceManager {
public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
/**
- * The length limit of Association tag.
- * @hide
- */
- private static final int ASSOCIATION_TAG_LENGTH_LIMIT = 1024;
-
- /**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
*
@@ -1229,7 +1223,6 @@ public final class CompanionDeviceManager {
}
}
- // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Register to receive callbacks whenever the associated device comes in and out of range.
*
@@ -1261,7 +1254,12 @@ public final class CompanionDeviceManager {
*
* @throws DeviceNotAssociatedException if the given device was not previously associated
* with this app.
+ *
+ * @deprecated use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest)}
+ * instead.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @Deprecated
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void startObservingDevicePresence(@NonNull String deviceAddress)
throws DeviceNotAssociatedException {
@@ -1288,7 +1286,7 @@ public final class CompanionDeviceManager {
callingUid, callingPid);
}
}
- // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
+
/**
* Unregister for receiving callbacks whenever the associated device comes in and out of range.
*
@@ -1305,7 +1303,12 @@ public final class CompanionDeviceManager {
*
* @throws DeviceNotAssociatedException if the given device was not previously associated
* with this app.
+ *
+ * @deprecated use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest)}
+ * instead.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @Deprecated
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void stopObservingDevicePresence(@NonNull String deviceAddress)
throws DeviceNotAssociatedException {
@@ -1771,57 +1774,25 @@ public final class CompanionDeviceManager {
}
/**
- * Sets the {@link AssociationInfo#getTag() tag} for this association.
- *
- * <p>The length of the tag must be at most 1024 characters to save disk space.
- *
- * <p>This allows to store useful information about the associated devices.
- *
- * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
- * of the companion device recorded by CompanionDeviceManager
- * @param tag the tag of this association
- */
- @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
- @UserHandleAware
- public void setAssociationTag(int associationId, @NonNull String tag) {
- if (mService == null) {
- Log.w(TAG, "CompanionDeviceManager service is not available.");
- return;
- }
-
- Objects.requireNonNull(tag, "tag cannot be null");
-
- if (tag.length() > ASSOCIATION_TAG_LENGTH_LIMIT) {
- throw new IllegalArgumentException("Length of the tag must be at most"
- + ASSOCIATION_TAG_LENGTH_LIMIT + " characters");
- }
-
- try {
- mService.setAssociationTag(associationId, tag);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Clears the {@link AssociationInfo#getTag() tag} for this association.
+ * Sets the {@link DeviceId deviceId} for this association.
*
- * <p>The tag will be set to null for this association when calling this API.
+ * <p>This device id helps the system uniquely identify your device for efficient device
+ * management and prevents duplicate entries.
*
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
- * of the companion device recorded by CompanionDeviceManager
- * @see CompanionDeviceManager#setAssociationTag(int, String)
+ * of the companion device recorded by CompanionDeviceManager.
+ * @param deviceId to be used as device identifier to represent the associated device.
*/
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
- public void clearAssociationTag(int associationId) {
+ public void setDeviceId(int associationId, @Nullable DeviceId deviceId) {
if (mService == null) {
Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
try {
- mService.clearAssociationTag(associationId);
+ mService.setDeviceId(associationId, deviceId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index db080fcc7702..316d129bd6b9 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -247,12 +247,14 @@ public abstract class CompanionDeviceService extends Service {
.detachSystemDataTransport(associationId);
}
- // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by the system when an associated device is nearby or connected.
*
* @param associationInfo A record for the companion device.
+ * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @Deprecated
@MainThread
public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
if (!associationInfo.isSelfManaged()) {
@@ -260,12 +262,14 @@ public abstract class CompanionDeviceService extends Service {
}
}
- // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by the system when an associated device is out of range or disconnected.
*
* @param associationInfo A record for the companion device.
+ * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @Deprecated
@MainThread
public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
if (!associationInfo.isSelfManaged()) {
diff --git a/core/java/android/companion/DeviceId.aidl b/core/java/android/companion/DeviceId.aidl
new file mode 100644
index 000000000000..d60d5f40eb6a
--- /dev/null
+++ b/core/java/android/companion/DeviceId.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.companion;
+
+parcelable DeviceId; \ No newline at end of file
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
new file mode 100644
index 000000000000..d9514a02c2b4
--- /dev/null
+++ b/core/java/android/companion/DeviceId.java
@@ -0,0 +1,214 @@
+/*
+ * 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.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * A device id represents a device identifier managed by the companion app.
+ */
+@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
+public final class DeviceId implements Parcelable {
+ /**
+ * The length limit of custom id.
+ */
+ private static final int CUSTOM_ID_LENGTH_LIMIT = 1024;
+
+ private final String mCustomId;
+ private final MacAddress mMacAddress;
+
+ /**
+ * @hide
+ */
+ public DeviceId(@Nullable String customId, @Nullable MacAddress macAddress) {
+ mCustomId = customId;
+ mMacAddress = macAddress;
+ }
+
+ /**
+ * Returns true if two Device ids are represent the same device. False otherwise.
+ * @hide
+ */
+ public boolean isSameDevice(@Nullable DeviceId other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (this.mCustomId != null && other.mCustomId != null) {
+ return this.mCustomId.equals(other.mCustomId);
+ }
+ if (this.mMacAddress != null && other.mMacAddress != null) {
+ return this.mMacAddress.equals(other.mMacAddress);
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getMacAddressAsString() {
+ return mMacAddress != null ? mMacAddress.toString().toUpperCase(Locale.US) : null;
+ }
+
+ /**
+ * @return the custom id that managed by the companion app.
+ */
+ @Nullable
+ public String getCustomId() {
+ return mCustomId;
+ }
+
+ /**
+ * @return the mac address that managed by the companion app.
+ */
+ @Nullable
+ public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mCustomId != null) {
+ dest.writeInt(1);
+ dest.writeString8(mCustomId);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeTypedObject(mMacAddress, 0);
+
+ }
+
+ private DeviceId(@NonNull Parcel in) {
+ int flg = in.readInt();
+ if (flg == 1) {
+ mCustomId = in.readString8();
+ } else {
+ mCustomId = null;
+ }
+ mMacAddress = in.readTypedObject(MacAddress.CREATOR);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<DeviceId> CREATOR =
+ new Parcelable.Creator<DeviceId>() {
+ @Override
+ public DeviceId[] newArray(int size) {
+ return new DeviceId[size];
+ }
+
+ @Override
+ public DeviceId createFromParcel(@android.annotation.NonNull Parcel in) {
+ return new DeviceId(in);
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCustomId, mMacAddress);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DeviceId that)) return false;
+
+ return Objects.equals(mCustomId, that.mCustomId)
+ && Objects.equals(mMacAddress, that.mMacAddress);
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceId{"
+ + "," + "mCustomId= " + mCustomId
+ + "," + "mMacAddress= " + mMacAddress
+ + "}";
+ }
+
+ /**
+ * A builder for {@link DeviceId}
+ *
+ * <p>Calling apps must provide at least one of the following to identify
+ * the device: a custom ID using {@link #setCustomId(String)}, or a MAC address using
+ * {@link #setMacAddress(MacAddress)}.</p>
+ */
+ public static final class Builder extends OneTimeUseBuilder<DeviceId> {
+ private String mCustomId;
+ private MacAddress mMacAddress;
+
+ public Builder() {}
+
+ /**
+ * Sets the custom device id. This id is used by the Companion app to
+ * identify a specific device.
+ *
+ * @param customId the custom device id
+ * @throws IllegalArgumentException length of the custom id must more than 1024
+ * characters to save disk space.
+ */
+ @NonNull
+ public Builder setCustomId(@Nullable String customId) {
+ checkNotUsed();
+ if (customId != null
+ && customId.length() > CUSTOM_ID_LENGTH_LIMIT) {
+ throw new IllegalArgumentException("Length of the custom id must be at most "
+ + CUSTOM_ID_LENGTH_LIMIT + " characters");
+ }
+ this.mCustomId = customId;
+ return this;
+ }
+
+ /**
+ * Sets the mac address. This mac address is used by the Companion app to
+ * identify a specific device.
+ *
+ * @param macAddress the remote device mac address
+ * @throws IllegalArgumentException length of the custom id must more than 1024
+ * characters to save disk space.
+ */
+ @NonNull
+ public Builder setMacAddress(@Nullable MacAddress macAddress) {
+ checkNotUsed();
+ mMacAddress = macAddress;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public DeviceId build() {
+ markUsed();
+ if (mCustomId == null && mMacAddress == null) {
+ throw new IllegalArgumentException("At least one device id property must be"
+ + "non-null to build a DeviceId.");
+ }
+ return new DeviceId(mCustomId, mMacAddress);
+ }
+ }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index de3ddec05d27..a2b7dd9c3d0e 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -28,6 +28,7 @@ import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.os.ParcelUuid;
+import android.companion.DeviceId;
/**
@@ -134,9 +135,7 @@ interface ICompanionDeviceManager {
@EnforcePermission("MANAGE_COMPANION_DEVICES")
void enableSecureTransport(boolean enabled);
- void setAssociationTag(int associationId, String tag);
-
- void clearAssociationTag(int associationId);
+ void setDeviceId(int associationId, in DeviceId deviceId);
byte[] getBackupPayload(int userId);
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 2539a12a2a14..2b9f70037279 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -46,6 +46,7 @@ flag {
namespace: "companion"
description: "Unpair with an associated bluetooth device"
bug: "322237619"
+ is_exported: true
}
flag {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 367f1afc912b..f8ac27de1754 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -90,6 +90,12 @@ interface IVirtualDevice {
*/
boolean hasCustomAudioInputSupport();
+ /**
+ * Returns whether this device is allowed to create mirror displays.
+ */
+ boolean canCreateMirrorDisplays();
+
+ /*
/*
* Turns off all trusted non-mirror displays of the virtual device.
*/
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 767f52a92566..448793d12bcb 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -63,4 +63,11 @@ oneway interface IVirtualDeviceActivityListener {
* @param user The user associated with the activity.
*/
void onSecureWindowShown(int displayId, in ComponentName componentName, in UserHandle user);
+
+ /**
+ * Called when a secure surface is no longer shown on the device.
+ *
+ * @param displayId The display ID on which the secure surface was shown.
+ */
+ void onSecureWindowHidden(int displayId);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d63a4434d7d8..42c74414ecd9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -166,6 +166,20 @@ public class VirtualDeviceInternal {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void onSecureWindowHidden(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i).onSecureWindowHidden(displayId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
};
private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
@@ -617,6 +631,10 @@ public class VirtualDeviceInternal {
mExecutor.execute(() ->
mActivityListener.onSecureWindowShown(displayId, componentName, user));
}
+
+ public void onSecureWindowHidden(int displayId) {
+ mExecutor.execute(() -> mActivityListener.onSecureWindowHidden(displayId));
+ }
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6ea7834243a4..b3f09a98d623 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1288,6 +1288,17 @@ public final class VirtualDeviceManager {
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName,
@NonNull UserHandle user) {}
+
+ /**
+ * Called when a window with a secure surface is no longer shown on the device.
+ *
+ * @param displayId The display ID on which the window was shown before.
+ *
+ * @see Display#FLAG_SECURE
+ * @see WindowManager.LayoutParams#FLAG_SECURE
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ default void onSecureWindowHidden(int displayId) {}
}
/**
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index cc57dc05d6b1..e271cf4f60ec 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -946,6 +946,34 @@ public class ClipData implements Parcelable {
}
/**
+ * Make a clone of ClipData that only contains URIs. This reduces the size of data transfer over
+ * IPC and only retains important information for the purpose of verifying creator token of an
+ * Intent.
+ * @return a copy of ClipData with only URIs remained.
+ * @hide
+ */
+ public ClipData cloneOnlyUriItems() {
+ ArrayList<Item> items = null;
+ final int N = mItems.size();
+ for (int i = 0; i < N; i++) {
+ Item item = mItems.get(i);
+ if (item.getUri() != null) {
+ if (items == null) {
+ items = new ArrayList<>(N);
+ }
+ items.add(new Item(item.getUri()));
+ } else if (item.getIntent() != null) {
+ if (items == null) {
+ items = new ArrayList<>(N);
+ }
+ items.add(new Item(item.getIntent().cloneForCreatorToken()));
+ }
+ }
+ if (items == null || items.isEmpty()) return null;
+ return new ClipData(new ClipDescription("", new String[0]), items);
+ }
+
+ /**
* Create a new ClipData holding data of the type
* {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
*
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6e2ca2c109f1..6ec6a62ff639 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -18,6 +18,7 @@ package android.content;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
import android.annotation.AttrRes;
@@ -640,12 +641,15 @@ public abstract class Context {
public static final int BIND_FOREGROUND_SERVICE_WHILE_AWAKE = 0x02000000;
/**
- * @hide Flag for {@link #bindService}: For only the case where the binding
+ * Flag for {@link #bindService}: For only the case where the binding
* is coming from the system, set the process state to BOUND_FOREGROUND_SERVICE
* instead of the normal maximum of IMPORTANT_FOREGROUND. That is, this is
* saying that the process shouldn't participate in the normal power reduction
* modes (removing network access etc).
+ * @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
public static final int BIND_FOREGROUND_SERVICE = 0x04000000;
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02eed1a7553f..a6492d36cf8f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.net.Uri;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.os.BundleMerger;
@@ -6187,7 +6188,8 @@ public class Intent implements Parcelable, Cloneable {
* {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
* {@link #EXTRA_METADATA_TEXT},
* {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER},
- * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER}.
+ * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER},
+ * {@link #EXTRA_EXCLUDE_COMPONENTS}.
* </p>
*/
public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
@@ -7856,6 +7858,10 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int URI_ALLOW_UNSAFE = 1<<2;
+ static {
+ Bundle.intentClass = Intent.class;
+ }
+
// ---------------------------------------------------------------------
private String mAction;
@@ -7970,6 +7976,24 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Make a copy of all members important to identify an intent with its creator token.
+ * @hide
+ */
+ public @NonNull Intent cloneForCreatorToken() {
+ Intent clone = new Intent()
+ .setAction(this.mAction)
+ .setDataAndType(this.mData, this.mType)
+ .setPackage(this.mPackage)
+ .setComponent(this.mComponent)
+ .setFlags(this.mFlags & IMMUTABLE_FLAGS);
+ if (this.mClipData != null) {
+ clone.setClipData(this.mClipData.cloneOnlyUriItems());
+ }
+ clone.mCreatorTokenInfo = this.mCreatorTokenInfo;
+ return clone;
+ }
+
+ /**
* Create an intent with a given action. All other fields (data, type,
* class) are null. Note that the action <em>must</em> be in a
* namespace because Intents are used globally in the system -- for
@@ -11684,7 +11708,7 @@ public class Intent implements Parcelable, Cloneable {
Log.w(TAG, "Failure filling in extras", e);
}
}
- mCreatorTokenInfo = other.mCreatorTokenInfo;
+ fillInCreatorTokenInfo(other.mCreatorTokenInfo, changes);
if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
&& other.mContentUserHint != UserHandle.USER_CURRENT) {
mContentUserHint = other.mContentUserHint;
@@ -11692,6 +11716,45 @@ public class Intent implements Parcelable, Cloneable {
return changes;
}
+ // keep original creator token and merge nested intent keys.
+ private void fillInCreatorTokenInfo(CreatorTokenInfo otherCreatorTokenInfo, int changes) {
+ if (otherCreatorTokenInfo != null && otherCreatorTokenInfo.mNestedIntentKeys != null) {
+ if (mCreatorTokenInfo == null) {
+ mCreatorTokenInfo = new CreatorTokenInfo();
+ }
+ ArraySet<NestedIntentKey> otherNestedIntentKeys =
+ otherCreatorTokenInfo.mNestedIntentKeys;
+ if (mCreatorTokenInfo.mNestedIntentKeys == null) {
+ mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(otherNestedIntentKeys);
+ } else {
+ ArraySet<NestedIntentKey> otherKeys;
+ if ((changes & FILL_IN_CLIP_DATA) == 0) {
+ // If clip data is Not filled in from other, do not merge clip data keys.
+ otherKeys = new ArraySet<>();
+ int N = otherNestedIntentKeys.size();
+ for (int i = 0; i < N; i++) {
+ NestedIntentKey key = otherNestedIntentKeys.valueAt(i);
+ if (key.mType != NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+ otherKeys.add(key);
+ }
+ }
+ } else {
+ // If clip data is filled in from other, remove clip data keys from this
+ // creatorTokenInfo and then merge every key from the others.
+ int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+ for (int i = N - 1; i >= 0; i--) {
+ NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+ if (key.mType == NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+ mCreatorTokenInfo.mNestedIntentKeys.removeAt(i);
+ }
+ }
+ otherKeys = otherNestedIntentKeys;
+ }
+ mCreatorTokenInfo.mNestedIntentKeys.addAll(otherKeys);
+ }
+ }
+ }
+
/**
* Merge the extras data in this intent with that of other supplied intent using the
* strategy specified using {@code extrasMerger}.
@@ -12228,6 +12291,7 @@ public class Intent implements Parcelable, Cloneable {
private IBinder mCreatorToken;
// Stores all extra keys whose values are intents for a top level intent.
private ArraySet<NestedIntentKey> mNestedIntentKeys;
+
}
/**
@@ -12339,8 +12403,19 @@ public class Intent implements Parcelable, Cloneable {
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
if (mExtras != null && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
- Object value = mExtras.get(key);
-
+ Object value;
+ try {
+ value = mExtras.get(key);
+ } catch (BadParcelableException e) {
+ // This could happen when the key points to a LazyValue whose class cannot be
+ // found by the classLoader - A nested object more than 1 level deeper who is
+ // of type of a custom class could trigger this situation. In such case, we
+ // ignore it since it is not an intent. However, it could be a custom type that
+ // extends from Intent. If such an object is retrieved later in another
+ // component, then trying to launch such a custom class object will fail unless
+ // removeLaunchSecurityProtection() is called before it is launched.
+ value = null;
+ }
if (value instanceof Intent intent && !visited.contains(intent)) {
handleNestedIntent(intent, visited, new NestedIntentKey(
NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 23d3693628e7..7e0805137d0b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import static android.content.pm.SigningInfo.AppSigningSchemeVersion;
import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
@@ -59,6 +60,8 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -94,6 +97,7 @@ import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.SipDelegateManager;
import android.util.AndroidException;
import android.util.Log;
+import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.pm.parsing.PackageInfoCommonUtils;
@@ -3147,6 +3151,16 @@ public abstract class PackageManager {
public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000;
/**
+ * As the generated feature count is useful for classes that may not be compiled in the same
+ * annotation processing unit as PackageManager, we redeclare it here for visibility.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int SDK_FEATURE_COUNT =
+ com.android.internal.pm.SystemFeaturesMetadata.SDK_FEATURE_COUNT;
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
* audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
* lag in sound input or output.
@@ -11637,7 +11651,7 @@ public abstract class PackageManager {
private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
sApplicationInfoCache =
new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
"getApplicationInfo") {
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -11668,18 +11682,6 @@ public abstract class PackageManager {
sApplicationInfoCache.disableLocal();
}
- private static final PropertyInvalidatedCache.AutoCorker sCacheAutoCorker =
- new PropertyInvalidatedCache.AutoCorker(PermissionManager.CACHE_KEY_PACKAGE_INFO);
-
- /**
- * Invalidate caches of package and permission information system-wide.
- *
- * @hide
- */
- public static void invalidatePackageInfoCache() {
- sCacheAutoCorker.autoCork();
- }
-
// Some of the flags don't affect the query result, but let's be conservative and cache
// each combination of flags separately.
@@ -11738,7 +11740,7 @@ public abstract class PackageManager {
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 2048, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE,
"getPackageInfo") {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
@@ -11770,17 +11772,40 @@ public abstract class PackageManager {
/**
* Inhibit package info cache invalidations when correct.
*
- * @hide */
+ * @hide
+ */
public static void corkPackageInfoCache() {
- PropertyInvalidatedCache.corkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO);
+ sPackageInfoCache.corkInvalidations();
}
/**
* Enable package info cache invalidations.
*
- * @hide */
+ * @hide
+ */
public static void uncorkPackageInfoCache() {
- PropertyInvalidatedCache.uncorkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO);
+ sPackageInfoCache.uncorkInvalidations();
+ }
+
+ // This auto-corker is obsolete once the separate permission notifications feature is
+ // committed.
+ private static final PropertyInvalidatedCache.AutoCorker sCacheAutoCorker =
+ PropertyInvalidatedCache.separatePermissionNotificationsEnabled()
+ ? null
+ : new PropertyInvalidatedCache
+ .AutoCorker(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
+
+ /**
+ * Invalidate caches of package and permission information system-wide.
+ *
+ * @hide
+ */
+ public static void invalidatePackageInfoCache() {
+ if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+ sPackageInfoCache.invalidateCache();
+ } else {
+ sCacheAutoCorker.autoCork();
+ }
}
/**
@@ -11987,4 +12012,31 @@ public abstract class PackageManager {
throw new UnsupportedOperationException(
"parseServiceMetadata not implemented in subclass");
}
+
+ /**
+ * Verifies and returns the
+ * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+ * information of the file at the given path. This operation takes a few milliseconds.
+ *
+ * Unlike {@link #getPackageArchiveInfo(String, PackageInfoFlags)} with {@link
+ * #GET_SIGNING_CERTIFICATES}, this method does not require the file to be a package archive
+ * file.
+ *
+ * @throws SigningInfoException if the verification fails
+ *
+ * @hide
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @NonNull SigningInfo getVerifiedSigningInfo(@NonNull String path,
+ @AppSigningSchemeVersion int minAppSigningSchemeVersion) throws SigningInfoException {
+ ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<SigningDetails> result =
+ ApkSignatureVerifier.verify(input, path, minAppSigningSchemeVersion);
+ if (result.isError()) {
+ throw new SigningInfoException(
+ result.getErrorCode(), result.getErrorMessage(), result.getException());
+ }
+ return new SigningInfo(result.getResult());
+ }
}
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index 23daaf2d4138..e4fbd1f28dbb 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,14 +16,20 @@
package android.content.pm;
+import static android.content.pm.SigningDetails.SignatureSchemeVersion;
+
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.PublicKey;
import java.util.Collection;
@@ -31,6 +37,55 @@ import java.util.Collection;
* Information pertaining to the signing certificates used to sign a package.
*/
public final class SigningInfo implements Parcelable {
+ /**
+ * JAR signing (v1 scheme).
+ * See https://source.android.com/docs/security/features/apksigning#v1.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int VERSION_JAR = SignatureSchemeVersion.JAR;
+
+ /**
+ * APK signature scheme v2.
+ * See https://source.android.com/docs/security/features/apksigning/v2.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int VERSION_SIGNING_BLOCK_V2 = SignatureSchemeVersion.SIGNING_BLOCK_V2;
+
+ /**
+ * APK signature scheme v3.
+ * See https://source.android.com/docs/security/features/apksigning/v3.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int VERSION_SIGNING_BLOCK_V3 = SignatureSchemeVersion.SIGNING_BLOCK_V3;
+
+ /**
+ * APK signature scheme v4.
+ * See https://source.android.com/docs/security/features/apksigning/v4.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int VERSION_SIGNING_BLOCK_V4 = SignatureSchemeVersion.SIGNING_BLOCK_V4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"VERSION_"}, value = {
+ VERSION_JAR,
+ VERSION_SIGNING_BLOCK_V2,
+ VERSION_SIGNING_BLOCK_V3,
+ VERSION_SIGNING_BLOCK_V4,
+ })
+ public @interface AppSigningSchemeVersion {}
@NonNull
private final SigningDetails mSigningDetails;
@@ -198,6 +253,17 @@ public final class SigningInfo implements Parcelable {
return mSigningDetails;
}
+ /**
+ * Returns true if the signing certificates in this and other match exactly.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public boolean signersMatchExactly(@NonNull SigningInfo other) {
+ return mSigningDetails.signaturesMatchExactly(other.mSigningDetails);
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
new Parcelable.Creator<SigningInfo>() {
@Override
diff --git a/core/java/android/content/pm/SigningInfoException.java b/core/java/android/content/pm/SigningInfoException.java
new file mode 100644
index 000000000000..a81e07e73685
--- /dev/null
+++ b/core/java/android/content/pm/SigningInfoException.java
@@ -0,0 +1,50 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Indicates an error when verifying the
+ * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+ * information.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class SigningInfoException extends Exception {
+ private final int mCode;
+
+ /** @hide */
+ public SigningInfoException(int code, @NonNull String message, @Nullable Throwable cause) {
+ super(message, cause);
+ mCode = code;
+ }
+
+ /**
+ * Returns a code representing the cause, in one of the installation parse return codes in
+ * {@link PackageManager}.
+ */
+ @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+ public int getCode() {
+ return mCode;
+ }
+}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 44f2a4ca38e2..23f1ff8926df 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -158,6 +158,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUpdateSelfTestCases",
"options":[
{
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
index ba089f7fd33e..35e5c443e4b2 100644
--- a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -53,7 +53,14 @@ public final class DependencyInstallerCallback implements Parcelable {
* Callback to indicate that all the requested dependencies have been resolved and their
* sessions created. See {@link DependencyInstallerService#onDependenciesRequired}.
*
+ * The system will wait for the sessions to be installed before resuming the original session
+ * which requested dependency installation.
+ *
+ * If any of the session fails to install, the system may fail the original session. The caller
+ * is expected to handle clean up of any other pending sessions remanining.
+ *
* @param sessionIds the install session IDs for all requested dependencies
+ * @throws IllegalArgumentException if session id doesn't exist or has already failed.
*/
public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
try {
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
index 92d1d9e118e6..e4cf55d3ce5f 100644
--- a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -24,7 +24,7 @@ import java.util.List;
*
* {@hide}
*/
-oneway interface IDependencyInstallerCallback {
+interface IDependencyInstallerCallback {
/**
* Callback to indicate that all the requested dependencies have been resolved and have been
* committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}.
@@ -38,4 +38,4 @@ oneway interface IDependencyInstallerCallback {
* and any associated sessions have been abandoned.
*/
void onFailureToResolveAllDependencies();
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index fbe581c5d524..dfeee2a2335f 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -341,6 +341,7 @@ flag {
description: "Feature flag to introduce a new way to change the launcher badging."
bug: "364760703"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -353,13 +354,6 @@ flag {
}
flag {
- name: "support_minor_versions_in_minsdkversion"
- namespace: "package_manager_service"
- description: "Block app installations that specify an incompatible minor SDK version"
- bug: "377302905"
-}
-
-flag {
name: "app_compat_option_16kb"
is_exported: true
namespace: "devoptions_settings"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 833260a15c45..f29e2e8f10a3 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -363,6 +363,17 @@ flag {
is_fixed_read_only: true
}
+flag {
+ name: "cache_user_restrictions_read_only"
+ namespace: "multiuser"
+ description: "Cache hasUserRestriction to avoid unnecessary binder calls"
+ bug: "350419621"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
@@ -582,4 +593,5 @@ flag {
namespace: "profile_experiences"
description: "Add support for LauncherUserInfo configs"
bug: "346553745"
+ is_exported: true
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 153dd9a93490..e30f871b68eb 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -330,6 +330,36 @@ public class FrameworkParsingPackageUtils {
}
/**
+ * Check if a package is compatible with this platform with regards to its
+ * its minSdkVersionFull.
+ *
+ * @param minSdkVersionFullString A string representation of a major.minor version,
+ * e.g. "12.34"
+ * @param platformMinSdkVersionFull The major and minor version of the platform, i.e. the value
+ * of Build.VERSION.SDK_INT_FULL
+ * @param input A ParseInput object to report success or failure
+ */
+ public static ParseResult<Void> verifyMinSdkVersionFull(@NonNull String minSdkVersionFullString,
+ int platformMinSdkVersionFull, @NonNull ParseInput input) {
+ int minSdkVersionFull;
+ try {
+ minSdkVersionFull = Build.parseFullVersion(minSdkVersionFullString);
+ } catch (IllegalStateException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ e.getMessage());
+ }
+ if (minSdkVersionFull <= platformMinSdkVersionFull) {
+ return input.success(null);
+ }
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Requires newer sdk version "
+ + Build.fullVersionToString(minSdkVersionFull)
+ + " (current version is "
+ + Build.fullVersionToString(platformMinSdkVersionFull)
+ + ")");
+ }
+
+ /**
* Computes the targetSdkVersion to use at runtime. If the package is not compatible with this
* platform, populates {@code outError[0]} with an error message.
* <p>
diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig
index 61835c162c49..a26f48e30c7e 100644
--- a/core/java/android/content/pm/xr.aconfig
+++ b/core/java/android/content/pm/xr.aconfig
@@ -6,4 +6,5 @@ flag {
name: "xr_manifest_entries"
description: "Adds manifest entries used by Android XR"
bug: "364416355"
+ is_exported: true
} \ No newline at end of file
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d6620d19ccf0..afcdcb0fbcad 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -17,7 +17,9 @@
package android.content.res;
import android.annotation.Nullable;
+import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Canvas;
import android.graphics.Insets;
@@ -34,14 +36,17 @@ import android.util.MergedConfiguration;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import com.android.aconfig.annotations.VisibleForTesting;
+
/**
* CompatibilityInfo class keeps the information about the screen compatibility mode that the
* application is running under.
- *
- * {@hide}
+ *
+ * {@hide}
*/
@RavenwoodKeepWholeClass
public class CompatibilityInfo implements Parcelable {
@@ -129,12 +134,37 @@ public class CompatibilityInfo implements Parcelable {
*/
public final float applicationDensityInvertedScale;
+ /**
+ * Application's display rotation.
+ *
+ * <p>This field is used to sandbox fixed-orientation activities on displays or display areas
+ * with ignoreOrientationRequest, where the display orientation is more likely to be different
+ * from the orientation the activity requested (e.g. in desktop windowing, or letterboxed).
+ * Mainly set for activities which use the display rotation to orient their content, for example
+ * camera previews.
+ *
+ * <p>In the case of camera activities, assuming the wrong posture
+ * can lead to sideways or stretched previews. As part of camera compat treatment for desktop
+ * windowing, the app is sandboxed to believe that the app and the device are in the posture the
+ * app requested. For example for portrait fixed-orientation apps, the app is letterboxed to
+ * portrait, camera feed is cropped to portrait, and the display rotation is changed via this
+ * field, for example to {@link Surface.Rotation#ROTATION_0} on devices with portrait natural
+ * orientation. All of these parameters factor in common calculations for setting up the camera
+ * preview.
+ */
+ @Surface.Rotation
+ public int applicationDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
/** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
private static float sOverrideInvertedScale = 1f;
/** The process level override inverted density scale. See {@link #HAS_OVERRIDE_SCALING}. */
private static float sOverrideDensityInvertScale = 1f;
+ /** The process level override display rotation. */
+ @Surface.Rotation
+ private static int sOverrideDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
@UnsupportedAppUsage
@Deprecated
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
@@ -346,11 +376,16 @@ public class CompatibilityInfo implements Parcelable {
return (mCompatibilityFlags & HAS_OVERRIDE_SCALING) != 0;
}
+ /** Returns {@code true} if {@link #sOverrideDisplayRotation} should be set. */
+ public boolean isOverrideDisplayRotationRequired() {
+ return applicationDisplayRotation != WindowConfiguration.ROTATION_UNDEFINED;
+ }
+
@UnsupportedAppUsage
public boolean supportsScreen() {
return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
}
-
+
public boolean neverSupportsScreen() {
return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
}
@@ -618,6 +653,9 @@ public class CompatibilityInfo implements Parcelable {
}
public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
+ if (hasOverrideDisplayRotation()) {
+ applyDisplayRotationConfiguration(sOverrideDisplayRotation, inoutConfig);
+ }
if (hasOverrideScale()) return;
if (!supportsScreen()) {
// This is a larger screen device and the app is not
@@ -650,21 +688,42 @@ public class CompatibilityInfo implements Parcelable {
inoutConfig.windowConfiguration.scale(invertScale);
}
- /** @see #sOverrideInvertedScale */
- public static void applyOverrideScaleIfNeeded(Configuration config) {
- if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config);
+ /** Changes the WindowConfiguration display rotation for the given configuration. */
+ public static void applyDisplayRotationConfiguration(@Surface.Rotation int displayRotation,
+ Configuration inoutConfig) {
+ if (displayRotation != WindowConfiguration.ROTATION_UNDEFINED) {
+ inoutConfig.windowConfiguration.setDisplayRotation(displayRotation);
+ }
}
- /** @see #sOverrideInvertedScale */
- public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
- if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
- mergedConfig.getGlobalConfiguration());
- scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
- mergedConfig.getOverrideConfiguration());
- scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
- mergedConfig.getMergedConfiguration());
+ /** @see #sOverrideInvertedScale and #sOverrideDisplayRotation. */
+ public static void applyOverrideIfNeeded(Configuration config) {
+ if (hasOverrideDisplayRotation()) {
+ applyDisplayRotationConfiguration(sOverrideDisplayRotation, config);
+ }
+ if (hasOverrideScale()) {
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config);
+ }
+ }
+
+ /** @see #sOverrideInvertedScale and #sOverrideDisplayRotation. */
+ public static void applyOverrideIfNeeded(MergedConfiguration mergedConfig) {
+ if (hasOverrideDisplayRotation()) {
+ applyDisplayRotationConfiguration(sOverrideDisplayRotation,
+ mergedConfig.getGlobalConfiguration());
+ applyDisplayRotationConfiguration(sOverrideDisplayRotation,
+ mergedConfig.getOverrideConfiguration());
+ applyDisplayRotationConfiguration(sOverrideDisplayRotation,
+ mergedConfig.getMergedConfiguration());
+ }
+ if (hasOverrideScale()) {
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getGlobalConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getOverrideConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getMergedConfiguration());
+ }
}
/** Returns {@code true} if this process is in a environment with override scale. */
@@ -693,6 +752,22 @@ public class CompatibilityInfo implements Parcelable {
return sOverrideDensityInvertScale;
}
+ /** Returns {@code true} if this process is in a environment with override display rotation. */
+ private static boolean hasOverrideDisplayRotation() {
+ return sOverrideDisplayRotation != WindowConfiguration.ROTATION_UNDEFINED;
+ }
+
+ /** @see #sOverrideInvertedScale */
+ public static void setOverrideDisplayRotation(@Surface.Rotation int displayRotation) {
+ sOverrideDisplayRotation = displayRotation;
+ }
+
+ /** @see #sOverrideDisplayRotation */
+ @VisibleForTesting
+ public static int getOverrideDisplayRotation() {
+ return sOverrideDisplayRotation;
+ }
+
/**
* Compute the frame Rect for applications runs under compatibility mode.
*
@@ -747,18 +822,50 @@ public class CompatibilityInfo implements Parcelable {
if (this == o) {
return true;
}
- try {
- CompatibilityInfo oc = (CompatibilityInfo)o;
- if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
- if (applicationDensity != oc.applicationDensity) return false;
- if (applicationScale != oc.applicationScale) return false;
- if (applicationInvertedScale != oc.applicationInvertedScale) return false;
- if (applicationDensityScale != oc.applicationDensityScale) return false;
- if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false;
- return true;
- } catch (ClassCastException e) {
+
+ if (!(o instanceof CompatibilityInfo oc)) {
return false;
}
+
+ if (!isCompatibilityFlagsEqual(oc)) return false;
+ if (!isScaleEqual(oc)) return false;
+ if (!isDisplayRotationEqual(oc)) return false;
+ return true;
+ }
+
+ /**
+ * Checks the difference between this and given {@link CompatibilityInfo} o, and returns the
+ * combination of {@link ActivityInfo}.CONFIG_* changes that this difference should trigger.
+ */
+ public int getCompatibilityChangesForConfig(@Nullable CompatibilityInfo o) {
+ int changes = 0;
+ if (!isDisplayRotationEqual(o)) {
+ changes |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ }
+ if (!isScaleEqual(o) || !isCompatibilityFlagsEqual(o)) {
+ changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ return changes;
+ }
+
+ private boolean isScaleEqual(@Nullable CompatibilityInfo oc) {
+ if (oc == null) return false;
+ if (applicationDensity != oc.applicationDensity) return false;
+ if (applicationScale != oc.applicationScale) return false;
+ if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (applicationDensityScale != oc.applicationDensityScale) return false;
+ if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false;
+ return true;
+ }
+
+ private boolean isDisplayRotationEqual(@Nullable CompatibilityInfo oc) {
+ return oc != null && oc.applicationDisplayRotation == applicationDisplayRotation;
+ }
+
+ private boolean isCompatibilityFlagsEqual(@Nullable CompatibilityInfo oc) {
+ return oc != null && oc.mCompatibilityFlags == mCompatibilityFlags;
}
@Override
@@ -778,6 +885,10 @@ public class CompatibilityInfo implements Parcelable {
sb.append(" overrideDensityInvScale=");
sb.append(applicationDensityInvertedScale);
}
+ if (isOverrideDisplayRotationRequired()) {
+ sb.append(" overrideDisplayRotation=");
+ sb.append(applicationDisplayRotation);
+ }
if (!supportsScreen()) {
sb.append(" resizing");
}
@@ -800,6 +911,7 @@ public class CompatibilityInfo implements Parcelable {
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
result = 31 * result + Float.floatToIntBits(applicationDensityScale);
result = 31 * result + Float.floatToIntBits(applicationDensityInvertedScale);
+ result = 31 * result + applicationDisplayRotation;
return result;
}
@@ -816,6 +928,7 @@ public class CompatibilityInfo implements Parcelable {
dest.writeFloat(applicationInvertedScale);
dest.writeFloat(applicationDensityScale);
dest.writeFloat(applicationDensityInvertedScale);
+ dest.writeInt(applicationDisplayRotation);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -839,6 +952,7 @@ public class CompatibilityInfo implements Parcelable {
applicationInvertedScale = source.readFloat();
applicationDensityScale = source.readFloat();
applicationDensityInvertedScale = source.readFloat();
+ applicationDisplayRotation = source.readInt();
}
/**
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 6c35d106bfb7..d8d4e161006c 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -87,6 +87,16 @@ flag {
flag {
namespace: "credential_manager"
+ name: "framework_session_id_metric_bundle"
+ description: "Enables the session_id to be passed across to the UI logs"
+ bug: "379880133"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "credential_manager"
name: "clear_credentials_fix_enabled"
description: "Fixes bug in clearCredential API that causes indefinite suspension"
bug: "314926460"
@@ -104,3 +114,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "credential_manager"
+ name: "propagate_user_context_for_intent_creation"
+ description: "Propagates the user ID in which to find the right OEM UI component to launch"
+ bug: "373711451"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 7a23033754c5..73b6417a6ba4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -53,6 +53,7 @@ flag {
namespace: "biometrics_framework"
description: "This flag is for API changes related to Identity Check"
bug: "373424727"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 266efb7b759c..aba2345f28d8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1699,17 +1699,18 @@ public final class CameraManager {
}
if (context != null) {
- final ActivityManager activityManager =
- context.getSystemService(ActivityManager.class);
- for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
- final TaskInfo taskInfo = appTask.getTaskInfo();
- final int freeformCameraCompatMode =
- taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode;
- if (freeformCameraCompatMode != 0
- && taskInfo.topActivity != null
- && taskInfo.topActivity.getPackageName().equals(packageName)) {
- // WindowManager has requested rotation override.
- return getRotationOverrideForCompatFreeform(freeformCameraCompatMode);
+ final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+ if (activityManager != null) {
+ for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
+ final TaskInfo taskInfo = appTask.getTaskInfo();
+ final int freeformCameraCompatMode = taskInfo.appCompatTaskInfo
+ .cameraCompatTaskInfo.freeformCameraCompatMode;
+ if (freeformCameraCompatMode != 0
+ && taskInfo.topActivity != null
+ && taskInfo.topActivity.getPackageName().equals(packageName)) {
+ // WindowManager has requested rotation override.
+ return getRotationOverrideForCompatFreeform(freeformCameraCompatMode);
+ }
}
}
}
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 7e42f43056e1..29eaab8eebcc 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -86,7 +86,8 @@ public final class TotalCaptureResult extends CaptureResult {
mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
TotalCaptureResult physicalResult = new TotalCaptureResult(
- onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(),
+ onePhysicalResult.getCameraId(),
+ onePhysicalResult.getCameraMetadata(),
parent, extras, /*partials*/null, sessionId, new PhysicalCaptureResultInfo[0]);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
physicalResult);
@@ -115,7 +116,8 @@ public final class TotalCaptureResult extends CaptureResult {
mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>();
for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
TotalCaptureResult physicalResult = new TotalCaptureResult(
- onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(),
+ onePhysicalResult.getCameraId(),
+ onePhysicalResult.getCameraMetadata(),
parent, requestId, frameNumber, /*partials*/null, sessionId,
new PhysicalCaptureResultInfo[0]);
mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ea70abb55b48..34c0f7b19da9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -34,6 +34,7 @@ import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraExtensionCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraMetadataInfo;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
@@ -59,6 +60,8 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
@@ -129,6 +132,8 @@ public class CameraDeviceImpl extends CameraDevice
final Object mInterfaceLock = new Object(); // access from this class and Session only!
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
+ private long mFMQReader; // native fmq reader ptr
+
private final StateCallback mDeviceCallback;
private volatile StateCallbackKK mSessionStateCallback;
private final Executor mDeviceExecutor;
@@ -476,6 +481,9 @@ public class CameraDeviceImpl extends CameraDevice
if (mInError) return;
mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
+ Parcel resultParcel = Parcel.obtain();
+ mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0);
+ mFMQReader = nativeCreateFMQReader(resultParcel);
IBinder remoteDeviceBinder = remoteDevice.asBinder();
// For legacy camera device, remoteDevice is in the same process, and
@@ -1682,6 +1690,7 @@ public class CameraDeviceImpl extends CameraDevice
if (mRemoteDevice != null || mInError) {
mDeviceExecutor.execute(mCallOnClosed);
}
+ nativeClose(mFMQReader);
mRemoteDevice = null;
}
@@ -2416,27 +2425,61 @@ public class CameraDeviceImpl extends CameraDevice
}
}
}
+ private PhysicalCaptureResultInfo[] readMetadata(
+ PhysicalCaptureResultInfo[] srcPhysicalResults) {
+ PhysicalCaptureResultInfo[] retVal =
+ new PhysicalCaptureResultInfo[srcPhysicalResults.length];
+ int i = 0;
+ long fmqSize = 0;
+ for (PhysicalCaptureResultInfo srcPhysicalResult : srcPhysicalResults) {
+ CameraMetadataNative physicalCameraMetadata = null;
+ if (srcPhysicalResult.getCameraMetadataInfo().getTag() ==
+ CameraMetadataInfo.fmqSize) {
+ fmqSize = srcPhysicalResult.getCameraMetadataInfo().getFmqSize();
+ physicalCameraMetadata =
+ new CameraMetadataNative(nativeReadResultMetadata(mFMQReader, fmqSize));
+ } else {
+ physicalCameraMetadata = srcPhysicalResult.getCameraMetadata();
+ }
+ PhysicalCaptureResultInfo physicalResultInfo =
+ new PhysicalCaptureResultInfo(
+ srcPhysicalResult.getCameraId(), physicalCameraMetadata);
+ retVal[i] = physicalResultInfo;
+ i++;
+ }
+ return retVal;
+ }
@Override
- public void onResultReceived(CameraMetadataNative result,
+ public void onResultReceived(CameraMetadataInfo resultInfo,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
-
- if (DEBUG) {
- Log.v(TAG, "Received result frame " + frameNumber + " for id "
- + requestId);
- }
-
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
-
+ PhysicalCaptureResultInfo savedPhysicalResults[] = physicalResults;
+ CameraMetadataNative result;
+ if (resultInfo.getTag() == CameraMetadataInfo.fmqSize) {
+ CameraMetadataNative fmqMetadata =
+ new CameraMetadataNative(
+ nativeReadResultMetadata(mFMQReader, resultInfo.getFmqSize()));
+ result = fmqMetadata;
+ } else {
+ result = resultInfo.getMetadata();
+ }
+ physicalResults = readMetadata(savedPhysicalResults);
+ if (DEBUG) {
+ Log.v(TAG, "Received result frame " + frameNumber + " for id "
+ + requestId);
+ }
// Redirect device callback to the offline session in case we are in the middle
// of an offline switch
if (mOfflineSessionImpl != null) {
- mOfflineSessionImpl.getCallbacks().onResultReceived(result, resultExtras,
+ CameraMetadataInfo resultInfoOffline = CameraMetadataInfo.metadata(result);
+ mOfflineSessionImpl.getCallbacks().onResultReceived(resultInfoOffline,
+ resultExtras,
physicalResults);
return;
}
@@ -2824,6 +2867,11 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ private static native long nativeCreateFMQReader(Parcel resultQueue);
+ //TODO: Investigate adding FastNative b/62791857
+ private static native long nativeReadResultMetadata(long ptr, long metadataSize);
+ private static native void nativeClose(long ptr);
+
@Override
public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
synchronized(mInterfaceLock) {
@@ -2870,4 +2918,4 @@ public class CameraDeviceImpl extends CameraDevice
}
}
}
-}
+} \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index c0a5928a369b..d7b6f116e452 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -391,6 +391,18 @@ public class CameraMetadataNative implements Parcelable {
}
/**
+ * Take ownership of native metadata
+ */
+ public CameraMetadataNative(long metadataPtr) {
+ super();
+ mMetadataPtr = metadataPtr;
+ if (mMetadataPtr == 0) {
+ throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
+ }
+ updateNativeAllocation();
+ }
+
+ /**
* Move the contents from {@code other} into a new camera metadata instance.</p>
*
* <p>After this call, {@code other} will become empty.</p>
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 1769c4638805..e660d6aba854 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -28,6 +28,7 @@ import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallbac
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CameraMetadataInfo;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
@@ -291,10 +292,10 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
}
@Override
- public void onResultReceived(CameraMetadataNative result,
+ public void onResultReceived(CameraMetadataInfo resultInfo,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
-
+ CameraMetadataNative result = resultInfo.getMetadata();
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 831c75ec5d33..a79e084b7f41 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -26,6 +26,7 @@ import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.ExceptionUtils;
import android.hardware.camera2.utils.SubmitInfo;
+import android.hardware.common.fmq.MQDescriptor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -313,4 +314,15 @@ public class ICameraDeviceUserWrapper {
throw ExceptionUtils.throwAsPublicException(e);
}
}
-}
+
+ public MQDescriptor<Byte, Byte> getCaptureResultMetadataQueue() throws CameraAccessException {
+ try {
+ return mRemoteDevice.getCaptureResultMetadataQueue();
+ } catch (ServiceSpecificException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
index 09619d0f5c7e..77296d1c1877 100644
--- a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
+++ b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
@@ -15,6 +15,7 @@
*/
package android.hardware.camera2.impl;
+import android.hardware.camera2.CameraMetadataInfo;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.os.Parcel;
@@ -25,7 +26,7 @@ import android.os.Parcelable;
*/
public class PhysicalCaptureResultInfo implements Parcelable {
private String cameraId;
- private CameraMetadataNative cameraMetadata;
+ private CameraMetadataInfo cameraMetadataInfo;
public static final @android.annotation.NonNull Parcelable.Creator<PhysicalCaptureResultInfo> CREATOR =
new Parcelable.Creator<PhysicalCaptureResultInfo>() {
@@ -46,7 +47,7 @@ public class PhysicalCaptureResultInfo implements Parcelable {
public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) {
this.cameraId = cameraId;
- this.cameraMetadata = cameraMetadata;
+ this.cameraMetadataInfo = CameraMetadataInfo.metadata(cameraMetadata);
}
@Override
@@ -57,13 +58,12 @@ public class PhysicalCaptureResultInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(cameraId);
- cameraMetadata.writeToParcel(dest, flags);
+ cameraMetadataInfo.writeToParcel(dest, flags);
}
public void readFromParcel(Parcel in) {
cameraId = in.readString();
- cameraMetadata = new CameraMetadataNative();
- cameraMetadata.readFromParcel(in);
+ cameraMetadataInfo = CameraMetadataInfo.CREATOR.createFromParcel(in);
}
public String getCameraId() {
@@ -71,6 +71,11 @@ public class PhysicalCaptureResultInfo implements Parcelable {
}
public CameraMetadataNative getCameraMetadata() {
- return cameraMetadata;
+ return cameraMetadataInfo.getMetadata();
}
-}
+
+ public CameraMetadataInfo getCameraMetadataInfo() {
+ return cameraMetadataInfo;
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 7efdd6dbdf41..1f12bbf4d074 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,7 +107,7 @@ public class HubEndpoint {
public void onSessionOpenRequest(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo)
+ @Nullable String serviceDescriptor)
throws RemoteException {
HubEndpointSession activeSession;
synchronized (mLock) {
@@ -128,16 +128,16 @@ public class HubEndpoint {
processSessionOpenRequestResult(
sessionId,
initiator,
- serviceInfo,
+ serviceDescriptor,
mLifecycleCallback.onSessionOpenRequest(
- initiator, serviceInfo)));
+ initiator, serviceDescriptor)));
}
}
private void processSessionOpenRequestResult(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo,
+ @Nullable String serviceDescriptor,
HubEndpointSessionResult result) {
if (result == null) {
throw new IllegalArgumentException(
@@ -145,7 +145,7 @@ public class HubEndpoint {
}
if (result.isAccepted()) {
- acceptSession(sessionId, initiator, serviceInfo);
+ acceptSession(sessionId, initiator, serviceDescriptor);
} else {
Log.i(
TAG,
@@ -162,7 +162,7 @@ public class HubEndpoint {
private void acceptSession(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
return;
@@ -187,7 +187,7 @@ public class HubEndpoint {
HubEndpoint.this,
mAssignedHubEndpointInfo,
initiator,
- serviceInfo);
+ serviceDescriptor);
try {
// oneway call to notify system service that the request is completed
mServiceToken.openSessionRequestComplete(sessionId);
@@ -334,7 +334,6 @@ public class HubEndpoint {
@Nullable IHubEndpointMessageCallback endpointMessageCallback,
@NonNull Executor messageCallbackExecutor) {
mPendingHubEndpointInfo = pendingEndpointInfo;
-
mLifecycleCallback = endpointLifecycleCallback;
mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
mMessageCallback = endpointMessageCallback;
@@ -387,7 +386,7 @@ public class HubEndpoint {
}
/** @hide */
- public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) {
+ public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
// TODO(b/378974199): Consider refactor these assertions
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
@@ -397,7 +396,7 @@ public class HubEndpoint {
HubEndpointSession newSession;
try {
// Request system service to assign session id.
- int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo);
+ int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
// Save the newly created session
synchronized (mLock) {
@@ -407,7 +406,7 @@ public class HubEndpoint {
HubEndpoint.this,
destinationInfo,
mAssignedHubEndpointInfo,
- serviceInfo);
+ serviceDescriptor);
mActiveSessions.put(sessionId, newSession);
}
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index cf952cbdbfdc..77f937ebeabc 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -19,6 +19,7 @@ package android.hardware.contexthub;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.hardware.location.ContextHubTransaction;
@@ -43,7 +44,7 @@ public class HubEndpointSession implements AutoCloseable {
@NonNull private final HubEndpoint mHubEndpoint;
@NonNull private final HubEndpointInfo mInitiator;
@NonNull private final HubEndpointInfo mDestination;
- @Nullable private final HubServiceInfo mServiceInfo;
+ @Nullable private final String mServiceDescriptor;
private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
@@ -53,12 +54,12 @@ public class HubEndpointSession implements AutoCloseable {
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
@NonNull HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
mId = id;
mHubEndpoint = hubEndpoint;
mDestination = destination;
mInitiator = initiator;
- mServiceInfo = serviceInfo;
+ mServiceDescriptor = serviceDescriptor;
}
/**
@@ -68,8 +69,11 @@ public class HubEndpointSession implements AutoCloseable {
* @return For messages that does not require a response, the transaction will immediately
* complete. For messages that requires a response, the transaction will complete after
* receiving the response for the message.
+ * @throws SecurityException if the application doesn't have the right permissions to send this
+ * message.
*/
@NonNull
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public ContextHubTransaction<Void> sendMessage(@NonNull HubMessage message) {
if (mIsClosed.get()) {
throw new IllegalStateException("Session is already closed.");
@@ -120,6 +124,7 @@ public class HubEndpointSession implements AutoCloseable {
* <p>When this function is invoked, the messaging associated with this session is invalidated.
* All futures messages targeted for this client are dropped.
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void close() {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
@@ -128,8 +133,8 @@ public class HubEndpointSession implements AutoCloseable {
}
/**
- * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there
- * is no service associated to this session.
+ * Get the service descriptor associated with this session. Null value indicates that there is
+ * no service associated to this session.
*
* <p>For hub initiated sessions, the object was previously used in as an argument for open
* request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
@@ -138,8 +143,8 @@ public class HubEndpointSession implements AutoCloseable {
* android.hardware.location.ContextHubManager#openSession}
*/
@Nullable
- public HubServiceInfo getServiceInfo() {
- return mServiceInfo;
+ public String getServiceDescriptor() {
+ return mServiceDescriptor;
}
@Override
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 1c98b4b3f4f5..b76b2271fe57 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -34,11 +34,12 @@ interface IContextHubEndpoint {
* Request system service to open a session with a specific destination.
*
* @param destination A valid HubEndpointInfo representing the destination.
+ * @param serviceDescriptor An optional descriptor of the service to scope this session to.
*
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
- int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
+ int openSession(in HubEndpointInfo destination, in @nullable String serviceDescriptor);
/**
* Request system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
index 1ae5fb9d28c1..63edda84bde5 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -29,9 +29,9 @@ oneway interface IContextHubEndpointCallback {
*
* @param sessionId An integer identifying the session, assigned by the initiator
* @param initiator HubEndpointInfo representing the requester
- * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session
+ * @param serviceDescriptor Nullable string representing the service associated with this session
*/
- void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo);
+ void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable String serviceDescriptor);
/**
* Request from system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index fe449bb5ce0e..698ed0adfd80 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -34,12 +34,12 @@ public interface IHubEndpointLifecycleCallback {
* Called when an endpoint is requesting a session be opened with another endpoint.
*
* @param requester The {@link HubEndpointInfo} object representing the requester
- * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with
- * this session. Null indicates that there is no service associated with this session.
+ * @param serviceDescriptor A string describing the service associated with this session. Null
+ * indicates that there is no service associated with this session.
*/
@NonNull
HubEndpointSessionResult onSessionOpenRequest(
- @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo);
+ @NonNull HubEndpointInfo requester, @Nullable String serviceDescriptor);
/**
* Called when a communication session is opened and ready to be used.
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 25327a9b1d52..7054c37cbc3b 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -24,6 +24,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -70,6 +71,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -1841,6 +1843,30 @@ public final class DisplayManager {
}
/**
+ * Register a listener to receive display topology updates.
+ * @param executor The executor specifying the thread on which the callbacks will be invoked
+ * @param listener The listener
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<DisplayTopology> listener) {
+ mGlobal.registerTopologyListener(executor, listener, ActivityThread.currentPackageName());
+ }
+
+ /**
+ * Unregister a display topology listener.
+ * @param listener The listener to unregister
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+ mGlobal.unregisterTopologyListener(listener);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1e66beea42a6..ffa546067eff 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -73,6 +74,7 @@ import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
/**
* Manager communication with the display manager service on behalf of
@@ -126,7 +128,7 @@ public final class DisplayManagerGlobal {
public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8;
public static final int EVENT_DISPLAY_STATE_CHANGED = 9;
- @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
+ @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
@@ -134,7 +136,8 @@ public final class DisplayManagerGlobal {
INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
- INTERNAL_EVENT_FLAG_DISPLAY_STATE
+ INTERNAL_EVENT_FLAG_DISPLAY_STATE,
+ INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalEventFlag {}
@@ -147,6 +150,7 @@ public final class DisplayManagerGlobal {
public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7;
+ public static final long INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED = 1L << 8;
@UnsupportedAppUsage
private static DisplayManagerGlobal sInstance;
@@ -164,6 +168,9 @@ public final class DisplayManagerGlobal {
private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<DisplayTopologyListenerDelegate> mTopologyListeners =
+ new CopyOnWriteArrayList<>();
+
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
private final ColorSpace mWideColorSpace;
private final OverlayProperties mOverlayProperties;
@@ -457,6 +464,18 @@ public final class DisplayManagerGlobal {
}
}
+ private void maybeLogAllTopologyListeners() {
+ if (!extraLogging()) {
+ return;
+ }
+ Slog.i(TAG, "Currently registered display topology listeners:");
+ int i = 0;
+ for (DisplayTopologyListenerDelegate d : mTopologyListeners) {
+ Slog.i(TAG, i + ": " + d);
+ i++;
+ }
+ }
+
/**
* Called when there is a display-related window configuration change. Reroutes the event from
* WindowManager to make sure the {@link Display} fields are up-to-date in the last callback.
@@ -502,9 +521,22 @@ public final class DisplayManagerGlobal {
| INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
| INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
+ if (!mTopologyListeners.isEmpty()) {
+ mask |= INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED;
+ }
return mask;
}
+ private DisplayTopologyListenerDelegate findTopologyListenerLocked(
+ @NonNull Consumer<DisplayTopology> listener) {
+ for (DisplayTopologyListenerDelegate delegate : mTopologyListeners) {
+ if (delegate.mListener == listener) {
+ return delegate;
+ }
+ }
+ return null;
+ }
+
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DisplayManagerCallback();
@@ -1316,6 +1348,9 @@ public final class DisplayManagerGlobal {
*/
@RequiresPermission(MANAGE_DISPLAYS)
public void setDisplayTopology(DisplayTopology topology) {
+ if (topology == null) {
+ throw new IllegalArgumentException("Topology must not be null");
+ }
try {
mDm.setDisplayTopology(topology);
} catch (RemoteException ex) {
@@ -1323,6 +1358,57 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * @see DisplayManager#registerTopologyListener
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<DisplayTopology> listener, String packageName) {
+ if (!Flags.displayTopology()) {
+ return;
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (extraLogging()) {
+ Slog.i(TAG, "Registering display topology listener: packageName=" + packageName);
+ }
+ synchronized (mLock) {
+ DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+ if (delegate == null) {
+ mTopologyListeners.add(new DisplayTopologyListenerDelegate(listener, executor,
+ packageName));
+ registerCallbackIfNeededLocked();
+ updateCallbackIfNeededLocked();
+ }
+ maybeLogAllTopologyListeners();
+ }
+ }
+
+ /**
+ * @see DisplayManager#unregisterTopologyListener
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+ if (!Flags.displayTopology()) {
+ return;
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (extraLogging()) {
+ Slog.i(TAG, "Unregistering display topology listener: " + listener);
+ }
+ synchronized (mLock) {
+ DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+ if (delegate != null) {
+ mTopologyListeners.remove(delegate);
+ updateCallbackIfNeededLocked();
+ }
+ }
+ maybeLogAllTopologyListeners();
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
@@ -1332,6 +1418,16 @@ public final class DisplayManagerGlobal {
}
handleDisplayEvent(displayId, event, false /* forceUpdate */);
}
+
+ @Override
+ public void onTopologyChanged(DisplayTopology topology) {
+ if (DEBUG) {
+ Log.d(TAG, "onTopologyChanged: " + topology);
+ }
+ for (DisplayTopologyListenerDelegate listener : mTopologyListeners) {
+ listener.onTopologyChanged(topology);
+ }
+ }
}
private static final class DisplayListenerDelegate {
@@ -1507,12 +1603,30 @@ public final class DisplayManagerGlobal {
mExecutor.execute(mCallback::onStopped);
}
}
+ }
- @Override // Binder call
- public void onRequestedBrightnessChanged(float brightness) {
- if (mCallback != null) {
- mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness));
+ private static final class DisplayTopologyListenerDelegate {
+ private final Consumer<DisplayTopology> mListener;
+ private final Executor mExecutor;
+ private final String mPackageName;
+
+ DisplayTopologyListenerDelegate(@NonNull Consumer<DisplayTopology> listener,
+ @NonNull @CallbackExecutor Executor executor, String packageName) {
+ mExecutor = executor;
+ mListener = listener;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayTopologyListener {packageName=" + mPackageName + "}";
+ }
+
+ void onTopologyChanged(DisplayTopology topology) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending topology update: " + topology);
}
+ mExecutor.execute(() -> mListener.accept(topology));
}
}
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index f00c3a53ad0c..1f7d426d9e6f 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -28,6 +28,7 @@ import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.IndentingPrintWriter;
+import android.util.MathUtils;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
@@ -283,6 +284,154 @@ public final class DisplayTopology implements Parcelable {
normalize();
}
+ /**
+ * Clamp offsets and remove any overlaps between displays.
+ */
+ public void normalize() {
+ if (mRoot == null) {
+ return;
+ }
+ clampOffsets(mRoot);
+
+ Map<TreeNode, RectF> bounds = new HashMap<>();
+ Map<TreeNode, Integer> depths = new HashMap<>();
+ Map<TreeNode, TreeNode> parents = new HashMap<>();
+ getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
+
+ // Sort the displays first by their depth in the tree, then by the distance of their top
+ // left point from the root display's origin (0, 0). This way we process the displays
+ // starting at the root and we push out a display if necessary.
+ Comparator<TreeNode> comparator = (d1, d2) -> {
+ if (d1 == d2) {
+ return 0;
+ }
+
+ int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
+ if (compareDepths != 0) {
+ return compareDepths;
+ }
+
+ RectF bounds1 = bounds.get(d1);
+ RectF bounds2 = bounds.get(d2);
+ return Double.compare(Math.hypot(bounds1.left, bounds1.top),
+ Math.hypot(bounds2.left, bounds2.top));
+ };
+ List<TreeNode> displays = new ArrayList<>(bounds.keySet());
+ displays.sort(comparator);
+
+ for (int i = 1; i < displays.size(); i++) {
+ TreeNode targetDisplay = displays.get(i);
+ TreeNode lastIntersectingSourceDisplay = null;
+ float lastOffsetX = 0;
+ float lastOffsetY = 0;
+
+ for (int j = 0; j < i; j++) {
+ TreeNode sourceDisplay = displays.get(j);
+ RectF sourceBounds = bounds.get(sourceDisplay);
+ RectF targetBounds = bounds.get(targetDisplay);
+
+ if (!RectF.intersects(sourceBounds, targetBounds)) {
+ continue;
+ }
+
+ // Find the offset by which to move the display. Pick the smaller one among the x
+ // and y axes.
+ float offsetX = targetBounds.left >= 0
+ ? sourceBounds.right - targetBounds.left
+ : sourceBounds.left - targetBounds.right;
+ float offsetY = targetBounds.top >= 0
+ ? sourceBounds.bottom - targetBounds.top
+ : sourceBounds.top - targetBounds.bottom;
+ if (Math.abs(offsetX) <= Math.abs(offsetY)) {
+ targetBounds.left += offsetX;
+ targetBounds.right += offsetX;
+ // We need to also update the offset in the tree
+ if (targetDisplay.mPosition == POSITION_TOP
+ || targetDisplay.mPosition == POSITION_BOTTOM) {
+ targetDisplay.mOffset += offsetX;
+ }
+ offsetY = 0;
+ } else {
+ targetBounds.top += offsetY;
+ targetBounds.bottom += offsetY;
+ // We need to also update the offset in the tree
+ if (targetDisplay.mPosition == POSITION_LEFT
+ || targetDisplay.mPosition == POSITION_RIGHT) {
+ targetDisplay.mOffset += offsetY;
+ }
+ offsetX = 0;
+ }
+
+ lastIntersectingSourceDisplay = sourceDisplay;
+ lastOffsetX = offsetX;
+ lastOffsetY = offsetY;
+ }
+
+ // Now re-parent the target display to the last intersecting source display if it no
+ // longer touches its parent.
+ if (lastIntersectingSourceDisplay == null) {
+ // There was no overlap.
+ continue;
+ }
+ TreeNode parent = parents.get(targetDisplay);
+ if (parent == lastIntersectingSourceDisplay) {
+ // The displays are moved in such a way that they're adjacent to the intersecting
+ // display. If the last intersecting display happens to be the parent then we
+ // already know that the display is adjacent to its parent.
+ continue;
+ }
+
+ RectF childBounds = bounds.get(targetDisplay);
+ RectF parentBounds = bounds.get(parent);
+ // Check that the edges are on the same line
+ boolean areTouching = switch (targetDisplay.mPosition) {
+ case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
+ case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
+ case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
+ case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+ default -> throw new IllegalStateException(
+ "Unexpected value: " + targetDisplay.mPosition);
+ };
+ // Check that the offset is within bounds
+ areTouching &= switch (targetDisplay.mPosition) {
+ case POSITION_LEFT, POSITION_RIGHT ->
+ childBounds.bottom + EPSILON >= parentBounds.top
+ && childBounds.top <= parentBounds.bottom + EPSILON;
+ case POSITION_TOP, POSITION_BOTTOM ->
+ childBounds.right + EPSILON >= parentBounds.left
+ && childBounds.left <= parentBounds.right + EPSILON;
+ default -> throw new IllegalStateException(
+ "Unexpected value: " + targetDisplay.mPosition);
+ };
+
+ if (!areTouching) {
+ // Re-parent the display.
+ parent.mChildren.remove(targetDisplay);
+ RectF lastIntersectingSourceDisplayBounds =
+ bounds.get(lastIntersectingSourceDisplay);
+ lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
+
+ if (lastOffsetX != 0) {
+ targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
+ targetDisplay.mOffset =
+ childBounds.top - lastIntersectingSourceDisplayBounds.top;
+ } else if (lastOffsetY != 0) {
+ targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
+ targetDisplay.mOffset =
+ childBounds.left - lastIntersectingSourceDisplayBounds.left;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return A deep copy of the topology that will not be modified by the system.
+ */
+ public DisplayTopology copy() {
+ TreeNode rootCopy = mRoot == null ? null : mRoot.copy();
+ return new DisplayTopology(rootCopy, mPrimaryDisplayId);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -433,143 +582,13 @@ public final class DisplayTopology implements Parcelable {
}
}
- /**
- * Update the topology to remove any overlaps between displays.
- */
- @VisibleForTesting
- public void normalize() {
- if (mRoot == null) {
- return;
- }
- Map<TreeNode, RectF> bounds = new HashMap<>();
- Map<TreeNode, Integer> depths = new HashMap<>();
- Map<TreeNode, TreeNode> parents = new HashMap<>();
- getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
-
- // Sort the displays first by their depth in the tree, then by the distance of their top
- // left point from the root display's origin (0, 0). This way we process the displays
- // starting at the root and we push out a display if necessary.
- Comparator<TreeNode> comparator = (d1, d2) -> {
- if (d1 == d2) {
- return 0;
- }
-
- int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
- if (compareDepths != 0) {
- return compareDepths;
- }
-
- RectF bounds1 = bounds.get(d1);
- RectF bounds2 = bounds.get(d2);
- return Double.compare(Math.hypot(bounds1.left, bounds1.top),
- Math.hypot(bounds2.left, bounds2.top));
- };
- List<TreeNode> displays = new ArrayList<>(bounds.keySet());
- displays.sort(comparator);
-
- for (int i = 1; i < displays.size(); i++) {
- TreeNode targetDisplay = displays.get(i);
- TreeNode lastIntersectingSourceDisplay = null;
- float lastOffsetX = 0;
- float lastOffsetY = 0;
-
- for (int j = 0; j < i; j++) {
- TreeNode sourceDisplay = displays.get(j);
- RectF sourceBounds = bounds.get(sourceDisplay);
- RectF targetBounds = bounds.get(targetDisplay);
-
- if (!RectF.intersects(sourceBounds, targetBounds)) {
- continue;
- }
-
- // Find the offset by which to move the display. Pick the smaller one among the x
- // and y axes.
- float offsetX = targetBounds.left >= 0
- ? sourceBounds.right - targetBounds.left
- : sourceBounds.left - targetBounds.right;
- float offsetY = targetBounds.top >= 0
- ? sourceBounds.bottom - targetBounds.top
- : sourceBounds.top - targetBounds.bottom;
- if (Math.abs(offsetX) <= Math.abs(offsetY)) {
- targetBounds.left += offsetX;
- targetBounds.right += offsetX;
- // We need to also update the offset in the tree
- if (targetDisplay.mPosition == POSITION_TOP
- || targetDisplay.mPosition == POSITION_BOTTOM) {
- targetDisplay.mOffset += offsetX;
- }
- offsetY = 0;
- } else {
- targetBounds.top += offsetY;
- targetBounds.bottom += offsetY;
- // We need to also update the offset in the tree
- if (targetDisplay.mPosition == POSITION_LEFT
- || targetDisplay.mPosition == POSITION_RIGHT) {
- targetDisplay.mOffset += offsetY;
- }
- offsetX = 0;
- }
-
- lastIntersectingSourceDisplay = sourceDisplay;
- lastOffsetX = offsetX;
- lastOffsetY = offsetY;
- }
-
- // Now re-parent the target display to the last intersecting source display if it no
- // longer touches its parent.
- if (lastIntersectingSourceDisplay == null) {
- // There was no overlap.
- continue;
- }
- TreeNode parent = parents.get(targetDisplay);
- if (parent == lastIntersectingSourceDisplay) {
- // The displays are moved in such a way that they're adjacent to the intersecting
- // display. If the last intersecting display happens to be the parent then we
- // already know that the display is adjacent to its parent.
- continue;
- }
-
- RectF childBounds = bounds.get(targetDisplay);
- RectF parentBounds = bounds.get(parent);
- // Check that the edges are on the same line
- boolean areTouching = switch (targetDisplay.mPosition) {
- case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
- case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
- case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
- case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
- default -> throw new IllegalStateException(
- "Unexpected value: " + targetDisplay.mPosition);
- };
- // Check that the offset is within bounds
- areTouching &= switch (targetDisplay.mPosition) {
- case POSITION_LEFT, POSITION_RIGHT ->
- childBounds.bottom + EPSILON >= parentBounds.top
- && childBounds.top <= parentBounds.bottom + EPSILON;
- case POSITION_TOP, POSITION_BOTTOM ->
- childBounds.right + EPSILON >= parentBounds.left
- && childBounds.left <= parentBounds.right + EPSILON;
- default -> throw new IllegalStateException(
- "Unexpected value: " + targetDisplay.mPosition);
- };
-
- if (!areTouching) {
- // Re-parent the display.
- parent.mChildren.remove(targetDisplay);
- RectF lastIntersectingSourceDisplayBounds =
- bounds.get(lastIntersectingSourceDisplay);
- lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
-
- if (lastOffsetX != 0) {
- targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
- targetDisplay.mOffset =
- childBounds.top - lastIntersectingSourceDisplayBounds.top;
- } else if (lastOffsetY != 0) {
- targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
- targetDisplay.mOffset =
- childBounds.left - lastIntersectingSourceDisplayBounds.left;
- }
- }
- }
+ /** Returns the graph representation of the topology */
+ public DisplayTopologyGraph getGraph() {
+ // TODO(b/364907904): implement
+ return new DisplayTopologyGraph(mPrimaryDisplayId,
+ new DisplayTopologyGraph.DisplayNode[] { new DisplayTopologyGraph.DisplayNode(
+ mRoot == null ? Display.DEFAULT_DISPLAY : mRoot.mDisplayId,
+ new DisplayTopologyGraph.AdjacentDisplay[0])});
}
/**
@@ -597,6 +616,24 @@ public final class DisplayTopology implements Parcelable {
return found;
}
+ /**
+ * Ensure that the offsets of all displays within the given tree are within bounds.
+ * @param display The starting node
+ */
+ private void clampOffsets(TreeNode display) {
+ if (display == null) {
+ return;
+ }
+ for (TreeNode child : display.mChildren) {
+ if (child.mPosition == POSITION_LEFT || child.mPosition == POSITION_RIGHT) {
+ child.mOffset = MathUtils.constrain(child.mOffset, -child.mHeight, display.mHeight);
+ } else if (child.mPosition == POSITION_TOP || child.mPosition == POSITION_BOTTOM) {
+ child.mOffset = MathUtils.constrain(child.mOffset, -child.mWidth, display.mWidth);
+ }
+ clampOffsets(child);
+ }
+ }
+
public static final class TreeNode implements Parcelable {
public static final int POSITION_LEFT = 0;
public static final int POSITION_TOP = 1;
@@ -694,6 +731,17 @@ public final class DisplayTopology implements Parcelable {
return Collections.unmodifiableList(mChildren);
}
+ /**
+ * @return A deep copy of the node that will not be modified by the system.
+ */
+ public TreeNode copy() {
+ TreeNode copy = new TreeNode(mDisplayId, mWidth, mHeight, mPosition, mOffset);
+ for (TreeNode child : mChildren) {
+ copy.mChildren.add(child.copy());
+ }
+ return copy;
+ }
+
@Override
public String toString() {
return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
diff --git a/core/java/android/hardware/display/DisplayTopologyGraph.java b/core/java/android/hardware/display/DisplayTopologyGraph.java
new file mode 100644
index 000000000000..938e6d108f5d
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopologyGraph.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.hardware.display;
+
+/**
+ * Graph of the displays in {@link android.hardware.display.DisplayTopology} tree.
+ *
+ * @hide
+ */
+public record DisplayTopologyGraph(int primaryDisplayId, DisplayNode[] displayNodes) {
+ /**
+ * Display in the topology
+ */
+ public record DisplayNode(
+ int displayId,
+ AdjacentDisplay[] adjacentDisplays) {}
+
+ /**
+ * Edge to adjacent display
+ */
+ public record AdjacentDisplay(
+ // The logical Id of this adjacent display
+ int displayId,
+ // Side of the other display which touches this adjacent display.
+ @DisplayTopology.TreeNode.Position
+ int position,
+ // How many px this display is shifted along the touchingSide, can be negative.
+ float offsetPx) {}
+}
diff --git a/core/java/android/hardware/display/IBrightnessListener.aidl b/core/java/android/hardware/display/IBrightnessListener.aidl
new file mode 100644
index 000000000000..f5d37435b287
--- /dev/null
+++ b/core/java/android/hardware/display/IBrightnessListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.hardware.display;
+
+/**
+ * Interface for notifying the display owner about brightness changes.
+ *
+ * @hide
+ */
+oneway interface IBrightnessListener {
+ /**
+ * Called when the display's brightness has changed.
+ *
+ * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the
+ * minimum supported brightness and value of {@code 1.0} indicates the maximum supported
+ * brightness.
+ */
+ void onBrightnessChanged(float brightness);
+}
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
index c50e3fb26156..d05a1b8400b0 100644
--- a/core/java/android/hardware/display/IDisplayManagerCallback.aidl
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -16,7 +16,10 @@
package android.hardware.display;
+import android.hardware.display.DisplayTopology;
+
/** @hide */
interface IDisplayManagerCallback {
oneway void onDisplayEvent(int displayId, int event);
+ oneway void onTopologyChanged(in DisplayTopology topology);
}
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
index 9cc0364f2729..c3490d177be2 100644
--- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
+++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
@@ -38,9 +38,4 @@ oneway interface IVirtualDisplayCallback {
* of the application to release() the virtual display.
*/
void onStopped();
-
- /**
- * Called when the virtual display's requested brightness has changed.
- */
- void onRequestedBrightnessChanged(float brightness);
}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 3b573ea98c27..32b640583734 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -16,8 +16,6 @@
package android.hardware.display;
import android.annotation.FlaggedApi;
-import android.annotation.FloatRange;
-import android.annotation.SystemApi;
import android.view.Display;
import android.view.Surface;
@@ -166,25 +164,5 @@ public final class VirtualDisplay {
* of the application to release() the virtual display.
*/
public void onStopped() { }
-
- /**
- * Called when the requested brightness of the display has changed.
- *
- * <p>The system may adjust the display's brightness based on user or app activity. This
- * callback will only be invoked if the display has an explicitly specified default
- * brightness value.</p>
- *
- * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
- * {@code 1.0} indicates the maximum supported brightness.</p>
- *
- * @see android.view.View#setKeepScreenOn(boolean)
- * @see android.view.WindowManager.LayoutParams#screenBrightness
- * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float)
- * @hide
- */
- @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
- @SystemApi
- public void onRequestedBrightnessChanged(
- @FloatRange(from = 0.0f, to = 1.0f) float brightness) {}
}
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 57d9d28a9d47..72570553f78a 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -18,11 +18,13 @@ package android.hardware.display;
import static android.view.Display.DEFAULT_DISPLAY;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.media.projection.MediaProjection;
@@ -38,6 +40,7 @@ import android.view.Surface;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Holds configuration used to create {@link VirtualDisplay} instances.
@@ -63,6 +66,8 @@ public final class VirtualDisplayConfig implements Parcelable {
private final DisplayCutout mDisplayCutout;
private final boolean mIgnoreActivitySizeRestrictions;
private final float mDefaultBrightness;
+ private final float mDimBrightness;
+ private final IBrightnessListener mBrightnessListener;
private VirtualDisplayConfig(
@NonNull String name,
@@ -79,7 +84,9 @@ public final class VirtualDisplayConfig implements Parcelable {
boolean isHomeSupported,
@Nullable DisplayCutout displayCutout,
boolean ignoreActivitySizeRestrictions,
- @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) {
+ @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness,
+ @FloatRange(from = 0.0f, to = 1.0f) float dimBrightness,
+ IBrightnessListener brightnessListener) {
mName = name;
mWidth = width;
mHeight = height;
@@ -95,6 +102,8 @@ public final class VirtualDisplayConfig implements Parcelable {
mDisplayCutout = displayCutout;
mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
mDefaultBrightness = defaultBrightness;
+ mDimBrightness = dimBrightness;
+ mBrightnessListener = brightnessListener;
}
/**
@@ -167,14 +176,33 @@ public final class VirtualDisplayConfig implements Parcelable {
* indicates the maximum supported brightness.</p>
*
* @see Builder#setDefaultBrightness(float)
- * @hide
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
- @SystemApi
public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() {
return mDefaultBrightness;
}
+ /**
+ * Returns the dim brightness of the display.
+ *
+ * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0}
+ * indicates the maximum supported brightness.</p>
+ *
+ * @see Builder#setDimBrightness(float)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public @FloatRange(from = 0.0f, to = 1.0f) float getDimBrightness() {
+ return mDimBrightness;
+ }
+
+ /**
+ * Returns the listener to get notified about changes in the display brightness.
+ * @hide
+ */
+ @Nullable
+ public IBrightnessListener getBrightnessListener() {
+ return mBrightnessListener;
+ }
/**
* Returns the unique identifier for the display. Shouldn't be displayed to the user.
@@ -266,6 +294,8 @@ public final class VirtualDisplayConfig implements Parcelable {
DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
dest.writeBoolean(mIgnoreActivitySizeRestrictions);
dest.writeFloat(mDefaultBrightness);
+ dest.writeFloat(mDimBrightness);
+ dest.writeStrongBinder(mBrightnessListener != null ? mBrightnessListener.asBinder() : null);
}
@Override
@@ -294,7 +324,9 @@ public final class VirtualDisplayConfig implements Parcelable {
&& mIsHomeSupported == that.mIsHomeSupported
&& mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions
&& Objects.equals(mDisplayCutout, that.mDisplayCutout)
- && mDefaultBrightness == that.mDefaultBrightness;
+ && mDefaultBrightness == that.mDefaultBrightness
+ && mDimBrightness == that.mDimBrightness
+ && Objects.equals(mBrightnessListener, that.mBrightnessListener);
}
@Override
@@ -303,7 +335,8 @@ public final class VirtualDisplayConfig implements Parcelable {
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
- mIgnoreActivitySizeRestrictions, mDefaultBrightness);
+ mIgnoreActivitySizeRestrictions, mDefaultBrightness, mDimBrightness,
+ mBrightnessListener);
return hashCode;
}
@@ -326,6 +359,7 @@ public final class VirtualDisplayConfig implements Parcelable {
+ " mDisplayCutout=" + mDisplayCutout
+ " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions
+ " mDefaultBrightness=" + mDefaultBrightness
+ + " mDimBrightness=" + mDimBrightness
+ ")";
}
@@ -345,6 +379,43 @@ public final class VirtualDisplayConfig implements Parcelable {
mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
mIgnoreActivitySizeRestrictions = in.readBoolean();
mDefaultBrightness = in.readFloat();
+ mDimBrightness = in.readFloat();
+ mBrightnessListener = IBrightnessListener.Stub.asInterface(in.readStrongBinder());
+ }
+
+ /**
+ * Listener for display brightness changes.
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public interface BrightnessListener {
+
+ /**
+ * Called when the display's brightness has changed.
+ *
+ * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the
+ * minimum supported brightness and value of {@code 1.0} indicates the maximum supported
+ * brightness.
+ */
+ void onBrightnessChanged(@FloatRange(from = 0.0f, to = 1.0f) float brightness);
+ }
+
+ private static class BrightnessListenerDelegate extends IBrightnessListener.Stub {
+
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final BrightnessListener mListener;
+
+ BrightnessListenerDelegate(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BrightnessListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onBrightnessChanged(float brightness) {
+ mExecutor.execute(() -> mListener.onBrightnessChanged(brightness));
+ }
}
@NonNull
@@ -380,6 +451,8 @@ public final class VirtualDisplayConfig implements Parcelable {
private DisplayCutout mDisplayCutout = null;
private boolean mIgnoreActivitySizeRestrictions = false;
private float mDefaultBrightness = 0.0f;
+ private float mDimBrightness = PowerManager.BRIGHTNESS_INVALID;
+ private IBrightnessListener mBrightnessListener = null;
/**
* Creates a new Builder.
@@ -575,24 +648,21 @@ public final class VirtualDisplayConfig implements Parcelable {
* Sets the default brightness of the display.
*
* <p>The system will use this brightness value whenever the display should be bright, i.e.
- * it is powered on and not dimmed due to user activity or app activity.</p>
+ * it is powered on and not modified due to user activity or app activity.</p>
*
* <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
* {@code 1.0} indicates the maximum supported brightness.</p>
*
* <p>If unset, defaults to {@code 0.0}</p>
*
+ * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
* @see android.view.View#setKeepScreenOn(boolean)
- * @see Builder#setDefaultBrightness(float)
- * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float)
- * @hide
+ * @see #setBrightnessListener(Executor, BrightnessListener)
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
- @SystemApi
@NonNull
public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
- if (brightness < PowerManager.BRIGHTNESS_MIN
- || brightness > PowerManager.BRIGHTNESS_MAX) {
+ if (!isValidBrightness(brightness)) {
throw new IllegalArgumentException(
"Virtual display default brightness must be in range [0.0, 1.0]");
}
@@ -601,10 +671,65 @@ public final class VirtualDisplayConfig implements Parcelable {
}
/**
+ * Sets the dim brightness of the display.
+ *
+ * <p>The system will use this brightness value whenever the display should be dim, i.e.
+ * it is powered on and dimmed due to user activity or app activity.</p>
+ *
+ * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+ * {@code 1.0} indicates the maximum supported brightness.</p>
+ *
+ * <p>If set, the default brightness must also be set to a value greater or equal to the
+ * dim brightness. If unset, defaults to the system default.</p>
+ *
+ * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
+ * @see Builder#setDefaultBrightness(float)
+ * @see #setBrightnessListener(Executor, BrightnessListener)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setDimBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
+ if (!isValidBrightness(brightness)) {
+ throw new IllegalArgumentException(
+ "Virtual display dim brightness must be in range [0.0, 1.0]");
+ }
+ mDimBrightness = brightness;
+ return this;
+ }
+
+ /**
+ * Sets the listener to get notified about changes in the display brightness.
+ *
+ * @param executor The executor where the callback is executed on.
+ * @param listener The listener to get notified when the display brightness has changed.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder") // The hidden getter returns the AIDL object
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setBrightnessListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BrightnessListener listener) {
+ mBrightnessListener = new BrightnessListenerDelegate(
+ Objects.requireNonNull(executor), Objects.requireNonNull(listener));
+ return this;
+ }
+
+ private boolean isValidBrightness(float brightness) {
+ return !Float.isNaN(brightness) && PowerManager.BRIGHTNESS_MIN <= brightness
+ && brightness <= PowerManager.BRIGHTNESS_MAX;
+ }
+
+ /**
* Builds the {@link VirtualDisplayConfig} instance.
+ *
+ * @throws IllegalArgumentException if the dim brightness is set to a value greater than
+ * the default brightness.
*/
@NonNull
public VirtualDisplayConfig build() {
+ if (isValidBrightness(mDimBrightness) && mDimBrightness > mDefaultBrightness) {
+ throw new IllegalArgumentException(
+ "The dim brightness must not be greater than the default brightness");
+ }
return new VirtualDisplayConfig(
mName,
mWidth,
@@ -620,7 +745,9 @@ public final class VirtualDisplayConfig implements Parcelable {
mIsHomeSupported,
mDisplayCutout,
mIgnoreActivitySizeRestrictions,
- mDefaultBrightness);
+ mDefaultBrightness,
+ mDimBrightness,
+ mBrightnessListener);
}
}
}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 114459e5e819..f8f7f5e0586e 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -27,7 +27,7 @@ import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
-import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadSystemGestureDisable;
import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
@@ -365,12 +365,12 @@ public class InputSettings {
}
/**
- * Returns true if the feature flag for touchpad tap dragging is enabled.
+ * Returns true if the feature flag for disabling system gestures on touchpads is enabled.
*
* @hide
*/
- public static boolean isTouchpadTapDraggingFeatureFlagEnabled() {
- return touchpadTapDragging();
+ public static boolean isTouchpadSystemGestureDisableFeatureFlagEnabled() {
+ return touchpadSystemGestureDisable();
}
/**
@@ -451,9 +451,6 @@ public class InputSettings {
* @hide
*/
public static boolean useTouchpadTapDragging(@NonNull Context context) {
- if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
- return false;
- }
return Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.TOUCHPAD_TAP_DRAGGING, 0, UserHandle.USER_CURRENT) == 1;
}
@@ -470,9 +467,6 @@ public class InputSettings {
*/
@RequiresPermission(Manifest.permission.WRITE_SETTINGS)
public static void setTouchpadTapDragging(@NonNull Context context, boolean enabled) {
- if (!isTouchpadTapDraggingFeatureFlagEnabled()) {
- return;
- }
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.TOUCHPAD_TAP_DRAGGING, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
@@ -530,6 +524,40 @@ public class InputSettings {
}
/**
+ * Returns true if system gestures (three- and four-finger swipes) should be enabled for
+ * touchpads.
+ *
+ * @param context The application context.
+ * @return Whether system gestures on touchpads are enabled
+ *
+ * @hide
+ */
+ public static boolean useTouchpadSystemGestures(@NonNull Context context) {
+ if (!isTouchpadSystemGestureDisableFeatureFlagEnabled()) {
+ return true;
+ }
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_SYSTEM_GESTURES, 1, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * Sets whether system gestures are enabled for touchpads.
+ *
+ * @param context The application context.
+ * @param enabled True to enable system gestures.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadSystemGesturesEnabled(@NonNull Context context, boolean enabled) {
+ if (!isTouchpadSystemGestureDisableFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_SYSTEM_GESTURES, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether a pointer icon will be shown over the location of a stylus pointer.
*
* @hide
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 711dc3a2cf7c..af756b9217d3 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -43,6 +43,8 @@ public final class KeyGestureEvent {
private static final int LOG_EVENT_UNSPECIFIED =
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+ // These values should not change and values should not be re-used as this data is persisted to
+ // long term storage and must be kept backwards compatible.
public static final int KEY_GESTURE_TYPE_UNSPECIFIED = 0;
public static final int KEY_GESTURE_TYPE_HOME = 1;
public static final int KEY_GESTURE_TYPE_RECENT_APPS = 2;
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0c89059a475a..eb7b409e77a6 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -37,13 +37,6 @@ flag {
flag {
namespace: "input_native"
- name: "touchpad_tap_dragging"
- description: "Offers a setting to enable touchpad tap dragging"
- bug: "321978150"
-}
-
-flag {
- namespace: "input_native"
name: "keyboard_glyph_map"
description: "Allows system to provide keyboard specific key drawables and shortcuts via config files"
bug: "345440920"
@@ -149,6 +142,13 @@ flag {
}
flag {
+ name: "touchpad_system_gesture_disable"
+ namespace: "input"
+ description: "Adds an accessibility setting to disable system navigation gestures (3- and 4-finger swipes) on touchpads"
+ bug: "353947750"
+}
+
+flag {
name: "enable_customizable_input_gestures"
namespace: "input"
description: "Enables keyboard shortcut customization support"
@@ -160,6 +160,7 @@ flag {
namespace: "input"
description: "Enables new 25Q2 keycodes"
bug: "365920375"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 117d8fe24809..d9888ad6cd8d 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -841,6 +841,7 @@ public final class ContextHubManager {
* @param endpointId The identifier of the hub endpoint.
* @param callback The callback to be invoked.
* @param executor The executor to invoke the callback on.
+ * @throws UnsupportedOperationException If the operation is not supported.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
@@ -881,6 +882,7 @@ public final class ContextHubManager {
* @param callback The callback to be invoked.
* @param executor The executor to invoke the callback on.
* @throws IllegalArgumentException if the serviceDescriptor is empty.
+ * @throws UnsupportedOperationException If the operation is not supported.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
@@ -911,6 +913,7 @@ public final class ContextHubManager {
*
* @param callback The callback previously registered.
* @throws IllegalArgumentException If the callback was not previously registered.
+ * @throws UnsupportedOperationException If the operation is not supported.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
@@ -1267,6 +1270,9 @@ public final class ContextHubManager {
* registration succeeds, the endpoint can receive notifications through the provided callback.
*
* @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
+ * @throws IllegalStateException if the registration failed, for example if too many endpoints
+ * are registered at the service
+ * @throws UnsupportedOperationException if endpoint registration is not supported
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
@@ -1306,16 +1312,18 @@ public final class ContextHubManager {
* ContextHubManager#registerEndpoint(HubEndpoint)}.
* @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
* endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
- * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with
- * this session. The information will be sent to the destination as part of open request.
+ * @param serviceDescriptor A string that describes the service associated with this session.
+ * The information will be sent to the destination as part of open request.
+ * @throws IllegalStateException if hubEndpoint was not successfully registered, or if there is
+ * insufficient capacity for creating a session.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void openSession(
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
- @NonNull HubServiceInfo serviceInfo) {
- hubEndpoint.openSession(destination, serviceInfo);
+ @NonNull String serviceDescriptor) {
+ hubEndpoint.openSession(destination, serviceDescriptor);
}
/**
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 2ba107805569..73c8e3e130a1 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -157,7 +157,7 @@ public class ConversionUtil {
SoundTrigger.RecognitionConfig apiConfig) {
RecognitionConfig aidlConfig = new RecognitionConfig();
aidlConfig.captureRequested = apiConfig.isCaptureRequested();
- // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers.
+ // apiConfig.isMultipleTriggersAllowed() is ignored by the lower layers.
aidlConfig.phraseRecognitionExtras =
new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()];
for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) {
@@ -178,7 +178,7 @@ public class ConversionUtil {
}
return new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(aidlConfig.captureRequested)
- .setAllowMultipleTriggers(false)
+ .setMultipleTriggersAllowed(false)
.setKeyphrases(keyphrases)
.setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
.setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 7745b036bcbe..7c4ddc669968 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1518,7 +1518,7 @@ public class SoundTrigger {
@FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API)
public static final class RecognitionConfig implements Parcelable {
private final boolean mCaptureRequested;
- private final boolean mAllowMultipleTriggers;
+ private final boolean mMultipleTriggersAllowed;
private final KeyphraseRecognitionExtra mKeyphrases[];
private final byte[] mData;
private final @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1529,7 +1529,7 @@ public class SoundTrigger {
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
*
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications who know about voice engine
@@ -1537,11 +1537,11 @@ public class SoundTrigger {
* @param audioCapabilities Bit field encoding of the AudioCapabilities. See
* {@link ModuleProperties.AudioCapabilities} for details.
*/
- private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ private RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
@ModuleProperties.AudioCapabilities int audioCapabilities) {
this.mCaptureRequested = captureRequested;
- this.mAllowMultipleTriggers = allowMultipleTriggers;
+ this.mMultipleTriggersAllowed = multipleTriggersAllowed;
this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.mData = data != null ? data : new byte[0];
this.mAudioCapabilities = audioCapabilities;
@@ -1553,7 +1553,7 @@ public class SoundTrigger {
*
* @deprecated Use {@link Builder} instead.
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications.
@@ -1563,9 +1563,9 @@ public class SoundTrigger {
@UnsupportedAppUsage
@Deprecated
@TestApi
- public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ public RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
- this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
+ this(captureRequested, multipleTriggersAllowed, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1593,8 +1593,8 @@ public class SoundTrigger {
* <p><b>Note:</b> This config flag is currently used at the service layer rather than by
* the DSP.
*/
- public boolean isAllowMultipleTriggers() {
- return mAllowMultipleTriggers;
+ public boolean isMultipleTriggersAllowed() {
+ return mMultipleTriggersAllowed;
}
/**
@@ -1627,19 +1627,19 @@ public class SoundTrigger {
private static RecognitionConfig fromParcel(Parcel in) {
boolean captureRequested = in.readBoolean();
- boolean allowMultipleTriggers = in.readBoolean();
+ boolean multipleTriggersAllowed = in.readBoolean();
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.createByteArray();
int audioCapabilities = in.readInt();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
- audioCapabilities);
+ return new RecognitionConfig(captureRequested, multipleTriggersAllowed, keyphrases,
+ data, audioCapabilities);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mCaptureRequested);
- dest.writeBoolean(mAllowMultipleTriggers);
+ dest.writeBoolean(mMultipleTriggersAllowed);
dest.writeTypedArray(mKeyphrases, flags);
dest.writeByteArray(mData);
dest.writeInt(mAudioCapabilities);
@@ -1653,7 +1653,7 @@ public class SoundTrigger {
@Override
public String toString() {
return "RecognitionConfig [captureRequested=" + mCaptureRequested
- + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases="
+ + ", multipleTriggersAllowed=" + mMultipleTriggersAllowed + ", keyphrases="
+ Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData)
+ ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]";
}
@@ -1670,7 +1670,7 @@ public class SoundTrigger {
if (mCaptureRequested != other.mCaptureRequested) {
return false;
}
- if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) {
+ if (mMultipleTriggersAllowed != other.mMultipleTriggersAllowed) {
return false;
}
if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) {
@@ -1690,7 +1690,7 @@ public class SoundTrigger {
final int prime = 31;
int result = 1;
result = prime * result + (mCaptureRequested ? 1 : 0);
- result = prime * result + (mAllowMultipleTriggers ? 1 : 0);
+ result = prime * result + (mMultipleTriggersAllowed ? 1 : 0);
result = prime * result + Arrays.hashCode(mKeyphrases);
result = prime * result + Arrays.hashCode(mData);
result = prime * result + mAudioCapabilities;
@@ -1702,7 +1702,7 @@ public class SoundTrigger {
*/
public static final class Builder {
private boolean mCaptureRequested;
- private boolean mAllowMultipleTriggers;
+ private boolean mMultipleTriggersAllowed;
@Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
@Nullable private byte[] mData;
private @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1725,12 +1725,12 @@ public class SoundTrigger {
/**
* Sets allow multiple triggers state.
- * @param allowMultipleTriggers Whether the service should restart listening after the
+ * @param multipleTriggersAllowed Whether the service should restart listening after the
* DSP triggers.
* @return the same Builder instance.
*/
- public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
- mAllowMultipleTriggers = allowMultipleTriggers;
+ public @NonNull Builder setMultipleTriggersAllowed(boolean multipleTriggersAllowed) {
+ mMultipleTriggersAllowed = multipleTriggersAllowed;
return this;
}
@@ -1779,7 +1779,7 @@ public class SoundTrigger {
public @NonNull RecognitionConfig build() {
RecognitionConfig config = new RecognitionConfig(
/* captureRequested= */ mCaptureRequested,
- /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+ /* multipleTriggersAllowed= */ mMultipleTriggersAllowed,
/* keyphrases= */ mKeyphrases,
/* data= */ mData,
/* audioCapabilities= */ mAudioCapabilities);
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index b719a7c6daac..9403f78383dd 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -30,6 +30,7 @@ flag {
namespace: "usb"
description: "Feature flag to enable exposing usb speed system api"
bug: "373653182"
+ is_exported: true
}
flag {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9b37533f5b02..9badbf8e2a1b 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -299,9 +299,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
if (event.hasNoModifiers()) {
return false;
}
- return event.hasModifiers(KeyEvent.META_CTRL_ON)
- || event.hasModifiers(KeyEvent.META_ALT_ON)
- || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION);
+ return event.isCtrlPressed()
+ || event.isAltPressed()
+ || event.isFunctionPressed()
+ || event.isMetaPressed();
}
private boolean needsVerification(KeyEvent event) {
diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags
index d5ed01496eba..32953c92d120 100644
--- a/core/java/android/net/EventLogTags.logtags
+++ b/core/java/android/net/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.net
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index a86396cd7c8d..0fedf8ecbb23 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -65,7 +65,7 @@ public class LocalSocket implements Closeable {
}
/**
- * Creates a AF_LOCAL/UNIX domain stream socket with given socket type
+ * Creates a AF_LOCAL/UNIX domain socket with the given socket type.
*
* @param sockType either {@link #SOCKET_DGRAM}, {@link #SOCKET_STREAM}
* or {@link #SOCKET_SEQPACKET}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index f7dc7906d50d..95b5f697969e 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -27,4 +27,13 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
+}
+
+flag {
+ name: "x509_extensions_certificate_transparency"
+ is_exported: true
+ namespace: "network_security"
+ description: "Flag to use checkServerTrusted to verify SCTs in OCSP and TLS Data"
+ bug: "319829948"
}
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index 280dad0284b6..b44f75a585d5 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,6 +16,13 @@
package android.net.http;
+import static com.android.org.conscrypt.flags.Flags.certificateTransparencyCheckservertrustedApi;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.platform.flags.Flags;
import android.security.net.config.UserCertificateSource;
import com.android.org.conscrypt.TrustManagerImpl;
@@ -24,6 +31,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.Collections;
import java.util.List;
import javax.net.ssl.X509TrustManager;
@@ -31,9 +39,9 @@ import javax.net.ssl.X509TrustManager;
/**
* X509TrustManager wrapper exposing Android-added features.
* <p>
- * The checkServerTrusted method allows callers to perform additional
- * verification of certificate chains after they have been successfully verified
- * by the platform.
+ * The checkServerTrusted methods allow callers to provide some additional
+ * context for the verification. This is particularly useful when an SSLEngine
+ * or SSLSocket is not available.
* </p>
*/
public class X509TrustManagerExtensions {
@@ -42,6 +50,7 @@ public class X509TrustManagerExtensions {
// Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
private final X509TrustManager mTrustManager;
private final Method mCheckServerTrusted;
+ private final Method mCheckServerTrustedOcspAndTlsData;
private final Method mIsSameTrustConfiguration;
/**
@@ -55,6 +64,7 @@ public class X509TrustManagerExtensions {
mDelegate = (TrustManagerImpl) tm;
mTrustManager = null;
mCheckServerTrusted = null;
+ mCheckServerTrustedOcspAndTlsData = null;
mIsSameTrustConfiguration = null;
return;
}
@@ -69,8 +79,19 @@ public class X509TrustManagerExtensions {
String.class);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Required method"
- + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+ + " checkServerTrusted(X509Certificate[], String, String) missing");
}
+ // Check that the OCSP and TlsData aware checkServerTrusted is present.
+ Method checkServerTrustedOcspAndTlsData = null;
+ try {
+ checkServerTrustedOcspAndTlsData = tm.getClass().getMethod("checkServerTrusted",
+ X509Certificate[].class,
+ Byte[].class,
+ Byte[].class,
+ String.class,
+ String.class);
+ } catch (ReflectiveOperationException ignored) { }
+ mCheckServerTrustedOcspAndTlsData = checkServerTrustedOcspAndTlsData;
// Get the option isSameTrustConfiguration method.
Method isSameTrustConfiguration = null;
try {
@@ -115,6 +136,65 @@ public class X509TrustManagerExtensions {
}
/**
+ * Verifies the given certificate chain.
+ *
+ * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a
+ * description of the chain and authType parameters. The final parameter, host, should be the
+ * hostname of the server.</p>
+ *
+ * <p>ocspData and tlsSctData may be provided to verify any Signed Certificate Timestamp (SCT)
+ * attached to the connection. These are ASN.1 octet strings (SignedCertificateTimestampList)
+ * as described in RFC 6962, Section 3.3. Note that SCTs embedded in the certificate chain
+ * will automatically be processed.
+ * </p>
+ *
+ * @throws CertificateException if the chain does not verify correctly.
+ * @throws IllegalArgumentException if the TrustManager is not compatible.
+ * @return the properly ordered chain used for verification as a list of X509Certificates.
+ */
+ @FlaggedApi(Flags.FLAG_X509_EXTENSIONS_CERTIFICATE_TRANSPARENCY)
+ @NonNull
+ public List<X509Certificate> checkServerTrusted(
+ @SuppressLint("ArrayReturn") @NonNull X509Certificate[] chain,
+ @Nullable byte[] ocspData,
+ @Nullable byte[] tlsSctData,
+ @NonNull String authType,
+ @NonNull String host) throws CertificateException {
+ List<X509Certificate> result;
+ if (mDelegate != null) {
+ if (certificateTransparencyCheckservertrustedApi()) {
+ result = mDelegate.checkServerTrusted(chain, ocspData, tlsSctData, authType, host);
+ return result == null ? Collections.emptyList() : result;
+ } else {
+ // The conscrypt mainline module does not have the required method.
+ throw new IllegalArgumentException("Required method"
+ + " checkServerTrusted(X509Certificate[], byte[], byte[], String, String)"
+ + " not available in TrustManagerImpl");
+ }
+ }
+ if (mCheckServerTrustedOcspAndTlsData == null) {
+ throw new IllegalArgumentException("Required method"
+ + " checkServerTrusted(X509Certificate[], byte[], byte[], String, String)"
+ + " missing");
+ }
+ try {
+ result = (List<X509Certificate>) mCheckServerTrustedOcspAndTlsData.invoke(mTrustManager,
+ ocspData, tlsSctData, chain, authType, host);
+ return result == null ? Collections.emptyList() : result;
+ } catch (IllegalAccessException e) {
+ throw new CertificateException("Failed to call checkServerTrusted", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof CertificateException) {
+ throw (CertificateException) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new CertificateException("checkServerTrusted failed", e.getCause());
+ }
+ }
+
+ /**
* Checks whether a CA certificate is added by an user.
*
* <p>Since {@link X509TrustManager#checkServerTrusted} may allow its parameter {@code chain} to
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index bf351ce07fe8..f53d1c4d191d 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -62,7 +62,11 @@ final public class DnsEvent {
return isSuccess;
}
if (eventCount == eventTypes.length) {
- resize((int) (1.4 * eventCount));
+ int resizeLength = (int) (1.4 * eventCount);
+ if (eventCount == resizeLength) {
+ resizeLength++;
+ }
+ resize(resizeLength);
}
eventTypes[eventCount] = eventType;
returnCodes[eventCount] = returnCode;
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index c2e9260879a8..84ca5ed4ab10 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -41,6 +41,8 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.View;
+import com.android.internal.util.FrameworkStatsLog;
+
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
@@ -401,7 +403,7 @@ public class Build {
* increase when the hardware manufacturer provides an OTA update.
* <p>
* This constant records the major version of Android. Use {@link
- * SDK_INT_FULL} if you need to consider the minor version of Android
+ * #SDK_INT_FULL} if you need to consider the minor version of Android
* as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
@@ -1546,6 +1548,57 @@ public class Build {
}
/**
+ * Convert a major.minor version String like "36.1" to an int that
+ * represents both major and minor version.
+ *
+ * @param version the String to parse
+ * @return an int encoding the major and minor version
+ * @throws IllegalArgumentException if the string could not be converted into an int
+ *
+ * @hide
+ */
+ @SuppressWarnings("FlaggedApi") // SDK_INT_MULTIPLIER is defined in this file
+ public static @SdkIntFull int parseFullVersion(@NonNull String version) {
+ int index = version.indexOf('.');
+ int major;
+ int minor = 0;
+ try {
+ if (index == -1) {
+ major = Integer.parseInt(version);
+ } else {
+ major = Integer.parseInt(version.substring(0, index));
+ minor = Integer.parseInt(version.substring(index + 1));
+ }
+ if (major < 0 || minor < 0) {
+ throw new NumberFormatException();
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("failed to parse '" + version
+ + "' as a major.minor version code");
+ }
+ return major * VERSION_CODES_FULL.SDK_INT_MULTIPLIER + minor;
+ }
+
+ /**
+ * Convert an int representing a major.minor version like SDK_INT_FULL to a
+ * human readable string. The returned string is only intended for debug
+ * and error messages.
+ *
+ * @param version the int to convert to a string
+ * @return a String representing the same major.minor version as the int passed in
+ * @throws IllegalArgumentException if {@code version} is negative
+ *
+ * @hide
+ */
+ public static String fullVersionToString(@SdkIntFull int version) {
+ if (version < 0) {
+ throw new IllegalArgumentException("failed to convert '" + version
+ + "' to string: not a valid major.minor version code");
+ }
+ return String.format("%d.%d", getMajorSdkVersion(version), getMinorSdkVersion(version));
+ }
+
+ /**
* The vendor API for 2024 Q2
*
* <p>For Android 14-QPR3 and later, the vendor API level is completely decoupled from the SDK
@@ -1615,11 +1668,14 @@ public class Build {
*/
@FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
public static @BackportedFixStatus int getBackportedFixStatus(long id) {
- if (id <= 0 || id > 1023) {
- return BACKPORTED_FIX_STATUS_UNKNOWN;
+ @BackportedFixStatus int status = BACKPORTED_FIX_STATUS_UNKNOWN;
+ int uid = Binder.getCallingUid();
+ if (id > 0 && id <= 1023) {
+ status = isBitSet(BackportedFixesProperties.alias_bitset(), (int) id)
+ ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN;
}
- return isBitSet(BackportedFixesProperties.alias_bitset(), (int) id)
- ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN;
+ FrameworkStatsLog.write(FrameworkStatsLog.BACKPORTED_FIX_STATUS_REPORTED, uid, id, status);
+ return status;
}
private static boolean isBitSet(List<Long> bitsetLongArray, int bitIndex) {
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 05bd10b053fe..819d58d9f059 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -70,6 +70,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*/
static final int FLAG_VERIFY_TOKENS_PRESENT = 1 << 13;
+ /**
+ * Indicates the bundle definitely contains an Intent.
+ */
+ static final int FLAG_HAS_INTENT = 1 << 14;
+
/**
* Status when the Bundle can <b>assert</b> that the underlying Parcel DOES NOT contain
@@ -118,6 +123,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
public static final Bundle EMPTY;
/**
+ * @hide
+ */
+ public static Class<?> intentClass;
+
+ /**
* Special extras used to denote extras have been stripped off.
* @hide
*/
@@ -388,6 +398,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
if ((bundle.mFlags & FLAG_HAS_BINDERS_KNOWN) == 0) {
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
+ mFlags |= bundle.mFlags & FLAG_HAS_INTENT;
}
/**
@@ -447,6 +458,16 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
}
+ /**
+ * Returns if the bundle definitely contains at least an intent. This method returns false does
+ * not guarantee the bundle does not contain a nested intent. An intent could still exist in a
+ * ParcelableArrayList, ParcelableArray, ParcelableList, a bundle in this bundle, etc.
+ * @hide
+ */
+ public boolean hasIntent() {
+ return (mFlags & FLAG_HAS_INTENT) != 0;
+ }
+
/** {@hide} */
@Override
public void putObject(@Nullable String key, @Nullable Object value) {
@@ -569,6 +590,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
mMap.put(key, value);
mFlags &= ~FLAG_HAS_FDS_KNOWN;
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
+ if (intentClass != null && intentClass.isInstance(value)) {
+ mFlags |= FLAG_HAS_INTENT;
+ }
}
/**
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 476968151e18..ce56a4f63a75 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -18,18 +18,27 @@ package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Process;
import android.os.UserHandle;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodRedirect;
import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
+import android.ravenwood.annotation.RavenwoodThrow;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.annotation.optimization.NeverCompile;
import java.io.FileDescriptor;
@@ -116,39 +125,89 @@ public final class MessageQueue {
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
- if (sIsProcessAllowedToUseConcurrent == null) {
- // Concurrent mode modifies behavior that is observable via reflection and is commonly
- // used by tests.
- // For now, we limit it to system processes to avoid breaking apps and their tests.
- boolean useConcurrent = UserHandle.isCore(Process.myUid());
+ initIsProcessAllowedToUseConcurrent();
+ mUseConcurrent = sIsProcessAllowedToUseConcurrent && !isInstrumenting();
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ private static void initIsProcessAllowedToUseConcurrent() {
+ if (sIsProcessAllowedToUseConcurrent != null) {
+ return;
+ }
+
+ if (RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+ sIsProcessAllowedToUseConcurrent = false;
+ return;
+ }
+
+ final String processName = Process.myProcessName();
+ if (processName == null) {
+ // Assume that this is a host-side test and avoid concurrent mode for now.
+ sIsProcessAllowedToUseConcurrent = false;
+ return;
+ }
+
+ // Concurrent mode modifies behavior that is observable via reflection and is commonly
+ // used by tests.
+ // For now, we limit it to system processes to avoid breaking apps and their tests.
+ sIsProcessAllowedToUseConcurrent = UserHandle.isCore(Process.myUid());
- // Some platform tests run in system UIDs.
+ if (sIsProcessAllowedToUseConcurrent) {
+ // Some platform tests run in core UIDs.
// Use this awful heuristic to detect them.
- if (useConcurrent) {
- final String processName = Process.myProcessName();
- if (processName == null
- || processName.contains("test")
- || processName.contains("Test")) {
- useConcurrent = false;
- }
+ if (processName.contains("test") || processName.contains("Test")) {
+ sIsProcessAllowedToUseConcurrent = false;
}
+ } else {
+ // Also explicitly allow SystemUI processes.
+ // SystemUI doesn't run in a core UID, but we want to give it the performance boost,
+ // and we know that it's safe to use the concurrent implementation in SystemUI.
+ sIsProcessAllowedToUseConcurrent =
+ processName.equals("com.android.systemui")
+ || processName.startsWith("com.android.systemui:");
+ // On Android distributions where SystemUI has a different process name,
+ // the above condition may need to be adjusted accordingly.
+ }
- // We can lift this restriction in the future after we've made it possible for test
- // authors to test Looper and MessageQueue without resorting to reflection.
+ // We can lift these restrictions in the future after we've made it possible for test
+ // authors to test Looper and MessageQueue without resorting to reflection.
- // Holdback study.
- if (useConcurrent && Flags.messageQueueForceLegacy()) {
- useConcurrent = false;
- }
+ // Holdback study.
+ if (sIsProcessAllowedToUseConcurrent && Flags.messageQueueForceLegacy()) {
+ sIsProcessAllowedToUseConcurrent = false;
+ }
+ }
- sIsProcessAllowedToUseConcurrent = useConcurrent;
- mUseConcurrent = useConcurrent;
- } else {
- mUseConcurrent = sIsProcessAllowedToUseConcurrent;
+ @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;
+ }
+ throw new IllegalStateException("Test-only API called not from a test!");
+ }
- mQuitAllowed = quitAllowed;
- mPtr = nativeInit();
+ private static void throwIfNotTest$ravenwood() {
+ return;
+ }
+
+ private static boolean isInstrumenting() {
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread == null) {
+ return false;
+ }
+ final Instrumentation instrumentation = activityThread.getInstrumentation();
+ return instrumentation != null && instrumentation.isInstrumenting();
}
@Override
@@ -171,12 +230,9 @@ public final class MessageQueue {
private static final class MatchDeliverableMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
- if (m.when <= when) {
- return true;
- }
- return false;
+ return n.mMessage.when <= when;
}
}
private final MatchDeliverableMessages mMatchDeliverableMessages =
@@ -323,7 +379,7 @@ public final class MessageQueue {
* @see OnFileDescriptorEventListener
* @see #removeOnFileDescriptorEventListener
*/
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
@OnFileDescriptorEventListener.Events int events,
@NonNull OnFileDescriptorEventListener listener) {
@@ -357,7 +413,7 @@ public final class MessageQueue {
* @see OnFileDescriptorEventListener
* @see #addOnFileDescriptorEventListener
*/
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
@@ -373,7 +429,7 @@ public final class MessageQueue {
}
}
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
OnFileDescriptorEventListener listener) {
final int fdNum = fd.getInt$();
@@ -496,7 +552,7 @@ public final class MessageQueue {
/* This is only read/written from the Looper thread. For use with Concurrent MQ */
private int mNextPollTimeoutMillis;
private boolean mMessageDirectlyQueued;
- private Message nextMessage() {
+ private Message nextMessage(boolean peek) {
int i = 0;
while (true) {
@@ -658,7 +714,7 @@ public final class MessageQueue {
if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
mMessageCounts.clearCounts();
if (found != null) {
- if (!removeFromPriorityQueue(found)) {
+ if (!peek && !removeFromPriorityQueue(found)) {
/*
* RemoveMessages() might be able to pull messages out from under us
* However we can detect that here and just loop around if it happens.
@@ -692,7 +748,7 @@ public final class MessageQueue {
mMessageDirectlyQueued = false;
nativePollOnce(ptr, mNextPollTimeoutMillis);
- Message msg = nextMessage();
+ Message msg = nextMessage(false);
if (msg != null) {
msg.markInUse();
return msg;
@@ -1011,8 +1067,9 @@ public final class MessageQueue {
}
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == null && m.arg1 == mBarrierToken) {
return true;
}
@@ -1215,10 +1272,155 @@ public final class MessageQueue {
return true;
}
+ private Message legacyPeekOrPop(boolean peek) {
+ synchronized (this) {
+ // Try to retrieve the next message. Return if found.
+ final long now = SystemClock.uptimeMillis();
+ Message prevMsg = null;
+ Message msg = mMessages;
+ if (msg != null && msg.target == null) {
+ // Stalled by a barrier. Find the next asynchronous message in the queue.
+ do {
+ prevMsg = msg;
+ msg = msg.next;
+ } while (msg != null && !msg.isAsynchronous());
+ }
+ if (msg != null) {
+ if (now >= msg.when) {
+ // Got a message.
+ mBlocked = false;
+ if (peek) {
+ return msg;
+ }
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
+ }
+ } else {
+ mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
+ }
+ }
+ msg.next = null;
+ msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the timestamp of the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery.
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ Long peekWhenForTest() {
+ throwIfNotTest();
+ Message ret;
+ if (mUseConcurrent) {
+ ret = nextMessage(true);
+ } else {
+ ret = legacyPeekOrPop(true);
+ }
+ return ret != null ? ret.when : null;
+ }
+
+ /**
+ * Return the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ @Nullable
+ Message popForTest() {
+ throwIfNotTest();
+ if (mUseConcurrent) {
+ return nextMessage(false);
+ } else {
+ return legacyPeekOrPop(false);
+ }
+ }
+
+ /**
+ * @return true if we are blocked on a sync barrier
+ */
+ boolean isBlockedOnSyncBarrier() {
+ throwIfNotTest();
+ if (mUseConcurrent) {
+ Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
+ MessageNode queueNode = iterateNext(queueIter);
+
+ if (queueNode.isBarrier()) {
+ long now = SystemClock.uptimeMillis();
+
+ /* Look for a deliverable async node. If one exists we are not blocked. */
+ Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator();
+ MessageNode asyncNode = iterateNext(asyncQueueIter);
+ if (asyncNode != null && now >= asyncNode.getWhen()) {
+ return false;
+ }
+ /*
+ * Look for a deliverable sync node. In this case, if one exists we are blocked
+ * since the barrier prevents delivery of the Message.
+ */
+ while (queueNode.isBarrier()) {
+ queueNode = iterateNext(queueIter);
+ }
+ if (queueNode != null && now >= queueNode.getWhen()) {
+ return true;
+ }
+
+ return false;
+ }
+ } else {
+ Message msg = mMessages;
+ if (msg != null && msg.target == null) {
+ Message iter = msg;
+ /* Look for a deliverable async node */
+ do {
+ iter = iter.next;
+ } while (iter != null && !iter.isAsynchronous());
+
+ long now = SystemClock.uptimeMillis();
+ if (iter != null && now >= iter.when) {
+ return false;
+ }
+ /*
+ * Look for a deliverable sync node. In this case, if one exists we are blocked
+ * since the barrier prevents delivery of the Message.
+ */
+ iter = msg;
+ do {
+ iter = iter.next;
+ } while (iter != null && (iter.target == null || iter.isAsynchronous()));
+
+ if (iter != null && now >= iter.when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ /* No barrier was found. */
+ return false;
+ }
+
private static final class MatchHandlerWhatAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.what == what && (object == null || m.obj == object)) {
return true;
}
@@ -1249,8 +1451,9 @@ public final class MessageQueue {
private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1282,8 +1485,9 @@ public final class MessageQueue {
private static final class MatchHandlerRunnableAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
return true;
}
@@ -1316,12 +1520,9 @@ public final class MessageQueue {
private static final class MatchHandler extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
- if (m.target == h) {
- return true;
- }
- return false;
+ return n.mMessage.target == h;
}
}
private final MatchHandler mMatchHandler = new MatchHandler();
@@ -1499,8 +1700,9 @@ public final class MessageQueue {
private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1562,8 +1764,9 @@ public final class MessageQueue {
private static final class MatchHandlerAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && (object == null || m.obj == object)) {
return true;
}
@@ -1623,8 +1826,9 @@ public final class MessageQueue {
private static final class MatchHandlerAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1730,7 +1934,7 @@ public final class MessageQueue {
private static final class MatchAllMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
return true;
}
@@ -1742,9 +1946,10 @@ public final class MessageQueue {
private static final class MatchAllFutureMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
- if (m.when > when) {
+ final Message m = n.mMessage;
+ if (m.when > when) {
return true;
}
return false;
@@ -2450,7 +2655,7 @@ public final class MessageQueue {
* This class is used to find matches for hasMessages() and removeMessages()
*/
private abstract static class MessageCompare {
- public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
Runnable r, long when);
}
@@ -2485,7 +2690,7 @@ public final class MessageQueue {
MessageNode p = (MessageNode) top;
while (true) {
- if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+ if (compare.compareMessage(p, h, what, object, r, when)) {
found = true;
if (DEBUG) {
Log.d(TAG_C, "stackHasMessages node matches");
@@ -2530,7 +2735,7 @@ public final class MessageQueue {
while (iterator.hasNext()) {
MessageNode msg = iterator.next();
- if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+ if (compare.compareMessage(msg, h, what, object, r, when)) {
if (removeMatches) {
found = true;
if (queue.remove(msg)) {
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index c2a47d767801..576c4cc5b442 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -16,9 +16,12 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodRedirect;
import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -364,6 +367,28 @@ public final class MessageQueue {
mPtr = nativeInit();
}
+ @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;
+ }
+ throw new IllegalStateException("Test-only API called not from a test!");
+ }
+
+ private static void throwIfNotTest$ravenwood() {
+ return;
+ }
+
@Override
protected void finalize() throws Throwable {
try {
@@ -384,8 +409,9 @@ public final class MessageQueue {
private static final class MatchDeliverableMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.when <= when) {
return true;
}
@@ -562,7 +588,7 @@ public final class MessageQueue {
private static final AtomicLong mMessagesDelivered = new AtomicLong();
private boolean mMessageDirectlyQueued;
- private Message nextMessage() {
+ private Message nextMessage(boolean peek) {
int i = 0;
while (true) {
@@ -724,7 +750,7 @@ public final class MessageQueue {
if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
mMessageCounts.clearCounts();
if (found != null) {
- if (!removeFromPriorityQueue(found)) {
+ if (!peek && !removeFromPriorityQueue(found)) {
/*
* RemoveMessages() might be able to pull messages out from under us
* However we can detect that here and just loop around if it happens.
@@ -993,8 +1019,9 @@ public final class MessageQueue {
}
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == null && m.arg1 == mBarrierToken) {
return true;
}
@@ -1039,6 +1066,79 @@ public final class MessageQueue {
}
}
+ private static final class MatchEarliestMessage extends MessageCompare {
+ MessageNode mEarliest = null;
+
+ @Override
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
+ if (mEarliest == null || mEarliest.mMessage.when > m.when) {
+ mEarliest = n;
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Get the timestamp of the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery.
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ Long peekWhenForTest() {
+ throwIfNotTest();
+ Message ret = nextMessage(true);
+ return ret != null ? ret.when : null;
+ }
+
+ /**
+ * Return the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ @Nullable
+ Message popForTest() {
+ throwIfNotTest();
+ return nextMessage(false);
+ }
+
+ /**
+ * @return true if we are blocked on a sync barrier
+ */
+ boolean isBlockedOnSyncBarrier() {
+ throwIfNotTest();
+ Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
+ MessageNode queueNode = iterateNext(queueIter);
+
+ if (queueNode.isBarrier()) {
+ long now = SystemClock.uptimeMillis();
+
+ /* Look for a deliverable async node. If one exists we are not blocked. */
+ Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator();
+ MessageNode asyncNode = iterateNext(asyncQueueIter);
+ if (asyncNode != null && now >= asyncNode.getWhen()) {
+ return false;
+ }
+ /*
+ * Look for a deliverable sync node. In this case, if one exists we are blocked
+ * since the barrier prevents delivery of the Message.
+ */
+ while (queueNode.isBarrier()) {
+ queueNode = iterateNext(queueIter);
+ }
+ if (queueNode != null && now >= queueNode.getWhen()) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
private StateNode getStateNode(StackNode node) {
if (node.isMessageNode()) {
return ((MessageNode) node).mBottomOfStack;
@@ -1058,7 +1158,7 @@ public final class MessageQueue {
* This class is used to find matches for hasMessages() and removeMessages()
*/
private abstract static class MessageCompare {
- public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
Runnable r, long when);
}
@@ -1167,8 +1267,9 @@ public final class MessageQueue {
private static final class MatchHandlerWhatAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.what == what && (object == null || m.obj == object)) {
return true;
}
@@ -1187,8 +1288,9 @@ public final class MessageQueue {
private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1208,8 +1310,9 @@ public final class MessageQueue {
private static final class MatchHandlerRunnableAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
return true;
}
@@ -1229,8 +1332,9 @@ public final class MessageQueue {
private static final class MatchHandler extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h) {
return true;
}
@@ -1268,8 +1372,9 @@ public final class MessageQueue {
private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1287,8 +1392,9 @@ public final class MessageQueue {
private static final class MatchHandlerAndObject extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h && (object == null || m.obj == object)) {
return true;
}
@@ -1305,8 +1411,9 @@ public final class MessageQueue {
private static final class MatchHandlerAndObjectEquals extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.target == h && (object == null || object.equals(m.obj))) {
return true;
}
@@ -1324,8 +1431,8 @@ public final class MessageQueue {
private static final class MatchAllMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
return true;
}
}
@@ -1336,8 +1443,9 @@ public final class MessageQueue {
private static final class MatchAllFutureMessages extends MessageCompare {
@Override
- public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
- long when) {
+ public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+ Runnable r, long when) {
+ final Message m = n.mMessage;
if (m.when > when) {
return true;
}
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
index 8e78b7e355f9..072c012d6db9 100644
--- a/core/java/android/os/CpuHeadroomParams.java
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -56,15 +56,9 @@ public final class CpuHeadroomParams {
*/
public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
- /**
- * Minimum CPU headroom calculation window size.
- */
- public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
-
- /**
- * Maximum CPU headroom calculation window size.
- */
- public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+ private static final int CALCULATION_WINDOW_MILLIS_MIN = 50;
+ private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000;
+ private static final int MAX_TID_COUNT = 5;
/**
* Sets the headroom calculation type.
@@ -99,20 +93,18 @@ public final class CpuHeadroomParams {
* Sets the headroom calculation window size in milliseconds.
* <p>
*
- * @param windowMillis the window size in milliseconds, ranged from
- * [{@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
- * {@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
- * the value, the larger fluctuation in value should be expected. The
- * default value can be retrieved from the
+ * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the
+ * window size, the larger fluctuation in the headroom value should be
+ * expected. The default value can be retrieved from the
* {@link #getCalculationWindowMillis}. The device will try to use the
* closest feasible window size to this param.
* @throws IllegalArgumentException if the window size is not in allowed range.
*/
public void setCalculationWindowMillis(
- @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
- CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
- if (windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
- || windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+ CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) {
throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
}
mInternal.calculationWindowMillis = windowMillis;
@@ -121,10 +113,10 @@ public final class CpuHeadroomParams {
/**
* Gets the headroom calculation window size in milliseconds.
* <p>
- * This will return the default value chosen by the device if not set.
+ * This will return the default value chosen by the device if the params is not set.
*/
- public @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
- CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() {
+ public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+ CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() {
return mInternal.calculationWindowMillis;
}
@@ -141,7 +133,7 @@ public final class CpuHeadroomParams {
* positive.
*/
public void setTids(@NonNull int... tids) {
- if (tids.length == 0 || tids.length > 5) {
+ if (tids.length == 0 || tids.length > MAX_TID_COUNT) {
throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length);
}
for (int tid : tids) {
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
index d572f965579b..12b209357d9c 100644
--- a/core/java/android/os/CpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -28,6 +28,5 @@ parcelable CpuHeadroomParamsInternal {
int[] tids;
int calculationWindowMillis = 1000;
CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
- CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
}
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
index 4dc98264e57b..126ee8cce3d0 100644
--- a/core/java/android/os/GpuHeadroomParams.java
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -54,15 +54,8 @@ public final class GpuHeadroomParams {
*/
public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
- /**
- * Minimum GPU headroom calculation window size.
- */
- public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
-
- /**
- * Maximum GPU headroom calculation window size.
- */
- public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+ private static final int CALCULATION_WINDOW_MILLIS_MIN = 50;
+ private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000;
/**
* Sets the headroom calculation type.
@@ -82,7 +75,7 @@ public final class GpuHeadroomParams {
/**
* Gets the headroom calculation type.
- * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+ * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if the params is not set.
*/
public @GpuHeadroomCalculationType int getCalculationType() {
@GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
@@ -97,20 +90,18 @@ public final class GpuHeadroomParams {
* Sets the headroom calculation window size in milliseconds.
* <p>
*
- * @param windowMillis the window size in milliseconds, ranged from
- * [{@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
- * {@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
- * the value, the larger fluctuation in value should be expected. The
- * default value can be retrieved from the
- * {@link #getCalculationWindowMillis}. If the device will try to use the
+ * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the
+ * window size, the larger fluctuation in the headroom value should be
+ * expected. The default value can be retrieved from the
+ * {@link #getCalculationWindowMillis}. The device will try to use the
* closest feasible window size to this param.
* @throws IllegalArgumentException if the window is invalid.
*/
public void setCalculationWindowMillis(
- @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
- GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
- if (windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
- || windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+ CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) {
throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
}
mInternal.calculationWindowMillis = windowMillis;
@@ -121,8 +112,8 @@ public final class GpuHeadroomParams {
* <p>
* This will return the default value chosen by the device if not set.
*/
- public @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
- GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() {
+ public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+ CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() {
return mInternal.calculationWindowMillis;
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 94768d111ca4..8f6a50843ddb 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -98,6 +98,7 @@ public class GraphicsEnvironment {
private static final int VULKAN_1_1 = 0x00401000;
private static final int VULKAN_1_2 = 0x00402000;
private static final int VULKAN_1_3 = 0x00403000;
+ private static final int VULKAN_1_4 = 0x00404000;
// Values for UPDATABLE_DRIVER_ALL_APPS
// 0: Default (Invalid values fallback to default as well)
@@ -179,6 +180,10 @@ public class GraphicsEnvironment {
private int getVulkanVersion(PackageManager pm) {
// PackageManager doesn't have an API to retrieve the version of a specific feature, and we
// need to avoid retrieving all system features here and looping through them.
+ if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_4)) {
+ return VULKAN_1_4;
+ }
+
if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_3)) {
return VULKAN_1_3;
}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index f1936b5e0ff9..4a14a8d0faf8 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -64,4 +64,10 @@ interface IHintManager {
* Get Maximum number of graphics pipeline threads allowed per-app.
*/
int getMaxGraphicsPipelineThreadsCount();
+
+ /**
+ * Used by the JNI to pass an interface to the SessionManager;
+ * for internal use only.
+ */
+ oneway void passSessionManagerBinder(in IBinder sessionManager);
}
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 6fd4f3c7c01a..e3f899de6d01 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -27,4 +27,9 @@ oneway interface IHintSession {
void sendHint(int hint);
void setMode(int mode, boolean enabled);
void reportActualWorkDuration2(in WorkDuration[] workDurations);
+
+ /**
+ * Used by apps to associate a session to a given set of layers
+ */
+ oneway void associateToLayers(in IBinder[] layerTokens);
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index e85e58039828..4cac4dee0bea 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -55,7 +55,7 @@ interface IPowerManager
void goToSleepWithDisplayId(int displayId, long time, int reason, int flags);
@UnsupportedAppUsage(maxTargetSdk = 28)
void nap(long time);
- float getBrightnessConstraint(int constraint);
+ float getBrightnessConstraint(int displayId, int constraint);
@UnsupportedAppUsage
boolean isInteractive();
boolean isDisplayInteractive(int displayId);
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 8db1567336d3..a2e9314f6436 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -400,10 +400,11 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
}
/**
- * This is a convenience class that encapsulates configuration information for a
- * cache. It may be supplied to the cache constructors in lieu of the other
- * parameters. The class captures maximum entry count, the module, the key, and the
- * api.
+ * This is a convenience class that encapsulates configuration information for a cache. It
+ * may be supplied to the cache constructors in lieu of the other parameters. The class
+ * captures maximum entry count, the module, the key, and the api. The key is used to
+ * invalidate the cache and may be shared by different caches. The api is a user-visible (in
+ * debug) name for the cache.
*
* There are three specific use cases supported by this class.
*
@@ -430,11 +431,8 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* @hide
*/
public static class Config {
- private final int mMaxEntries;
- @IpcDataCacheModule
- private final String mModule;
- private final String mApi;
- private final String mName;
+ final Args mArgs;
+ final String mName;
/**
* The list of cache names that were created extending this Config. If
@@ -452,12 +450,20 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
*/
private boolean mDisabled = false;
+ /**
+ * Fully construct a config.
+ */
+ private Config(@NonNull Args args, @NonNull String name) {
+ mArgs = args;
+ mName = name;
+ }
+
+ /**
+ *
+ */
public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
@NonNull String api, @NonNull String name) {
- mMaxEntries = maxEntries;
- mModule = module;
- mApi = api;
- mName = name;
+ this(new Args(module).api(api).maxEntries(maxEntries), name);
}
/**
@@ -473,7 +479,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* the parameter list.
*/
public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
- this(root.maxEntries(), root.module(), api, name);
+ this(root.mArgs.api(api), name);
}
/**
@@ -481,7 +487,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* the parameter list.
*/
public Config(@NonNull Config root, @NonNull String api) {
- this(root.maxEntries(), root.module(), api, api);
+ this(root.mArgs.api(api), api);
}
/**
@@ -490,26 +496,23 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* current process.
*/
public Config child(@NonNull String name) {
- final Config result = new Config(this, api(), name);
+ final Config result = new Config(mArgs, name);
registerChild(name);
return result;
}
- public final int maxEntries() {
- return mMaxEntries;
- }
-
- @IpcDataCacheModule
- public final @NonNull String module() {
- return mModule;
- }
-
- public final @NonNull String api() {
- return mApi;
+ /**
+ * Set the cacheNull behavior.
+ */
+ public Config cacheNulls(boolean enable) {
+ return new Config(mArgs.cacheNulls(enable), mName);
}
- public final @NonNull String name() {
- return mName;
+ /**
+ * Set the isolateUidss behavior.
+ */
+ public Config isolateUids(boolean enable) {
+ return new Config(mArgs.isolateUids(enable), mName);
}
/**
@@ -532,7 +535,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* Invalidate all caches that share this Config's module and api.
*/
public void invalidateCache() {
- IpcDataCache.invalidateCache(mModule, mApi);
+ IpcDataCache.invalidateCache(mArgs);
}
/**
@@ -564,8 +567,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
* @hide
*/
public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
- super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
- config.name(), computer);
+ super(config.mArgs, config.mName, computer);
}
/**
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index cae82d010132..10d090444c59 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -16,9 +16,14 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodRedirect;
@@ -99,6 +104,28 @@ public final class MessageQueue {
mPtr = nativeInit();
}
+ @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;
+ }
+ throw new IllegalStateException("Test-only API called not from a test!");
+ }
+
+ private static void throwIfNotTest$ravenwood() {
+ return;
+ }
+
@Override
protected void finalize() throws Throwable {
try {
@@ -713,6 +740,112 @@ public final class MessageQueue {
return true;
}
+ private Message legacyPeekOrPop(boolean peek) {
+ synchronized (this) {
+ // Try to retrieve the next message. Return if found.
+ final long now = SystemClock.uptimeMillis();
+ Message prevMsg = null;
+ Message msg = mMessages;
+ if (msg != null && msg.target == null) {
+ // Stalled by a barrier. Find the next asynchronous message in the queue.
+ do {
+ prevMsg = msg;
+ msg = msg.next;
+ } while (msg != null && !msg.isAsynchronous());
+ }
+ if (msg != null) {
+ if (now >= msg.when) {
+ // Got a message.
+ mBlocked = false;
+ if (peek) {
+ return msg;
+ }
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
+ }
+ } else {
+ mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
+ }
+ }
+ msg.next = null;
+ msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mAsyncMessageCount--;
+ }
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the timestamp of the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery.
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ Long peekWhenForTest() {
+ throwIfNotTest();
+ Message ret = legacyPeekOrPop(true);
+ return ret != null ? ret.when : null;
+ }
+
+ /**
+ * Return the next executable message in our priority queue.
+ * Returns null if there are no messages ready for delivery
+ *
+ * Caller must ensure that this doesn't race 'next' from the Looper thread.
+ */
+ @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+ @Nullable
+ Message popForTest() {
+ throwIfNotTest();
+ return legacyPeekOrPop(false);
+ }
+
+ /**
+ * @return true if we are blocked on a sync barrier
+ */
+ boolean isBlockedOnSyncBarrier() {
+ throwIfNotTest();
+ Message msg = mMessages;
+ if (msg != null && msg.target == null) {
+ Message iter = msg;
+ /* Look for a deliverable async node */
+ do {
+ iter = iter.next;
+ } while (iter != null && !iter.isAsynchronous());
+
+ long now = SystemClock.uptimeMillis();
+ if (iter != null && now >= iter.when) {
+ return false;
+ }
+ /*
+ * Look for a deliverable sync node. In this case, if one exists we are blocked
+ * since the barrier prevents delivery of the Message.
+ */
+ iter = msg;
+ do {
+ iter = iter.next;
+ } while (iter != null && (iter.target == null || iter.isAsynchronous()));
+
+ if (iter != null && now >= iter.when) {
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }
+
boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index f9789c19b0d5..8d353384f1e2 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -93,10 +93,9 @@ per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS
per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS
per-file CpuHeadroom*.java = file:/ADPF_OWNERS
per-file GpuHeadroom*.java = file:/ADPF_OWNERS
-per-file PerformanceHintManager.java = file:/ADPF_OWNERS
per-file WorkDuration.java = file:/ADPF_OWNERS
-per-file IHintManager.aidl = file:/ADPF_OWNERS
-per-file IHintSession.aidl = file:/ADPF_OWNERS
+per-file *Hint* = file:/ADPF_OWNERS
+per-file *Session* = file:/ADPF_OWNERS
# IThermal interfaces
per-file IThermal* = file:/THERMAL_OWNERS
@@ -119,9 +118,10 @@ per-file IpcDataCache.java = file:/PERFORMANCE_OWNERS
# Memory
per-file OomKillRecord.java = file:/MEMORY_OWNERS
-# MessageQueue
+# MessageQueue and related classes
per-file MessageQueue.java = mfasheh@google.com, shayba@google.com
per-file Message.java = mfasheh@google.com, shayba@google.com
+per-file TestLooperManager.java = mfasheh@google.com, shayba@google.com
# Stats
per-file IStatsBootstrapAtomService.aidl = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index bf7116d6a05b..cf473ec9c3ea 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -892,6 +892,12 @@ public final class Parcel {
/**
* Report whether the parcel contains any marshalled file descriptors.
+ *
+ * WARNING: Parcelable definitions change over time. Unless you define
+ * a Parcelable yourself OR the Parcelable explicitly guarantees that
+ * it would never include such objects, you should not expect the return
+ * value to stay the same, and your code should continue to work even
+ * if the return value changes.
*/
public boolean hasFileDescriptors() {
return nativeHasFileDescriptors(mNativePtr);
@@ -901,6 +907,12 @@ public final class Parcel {
* Report whether the parcel contains any marshalled file descriptors in the range defined by
* {@code offset} and {@code length}.
*
+ * WARNING: Parcelable definitions change over time. Unless you define
+ * a Parcelable yourself OR the Parcelable explicitly guarantees that
+ * it would never include such objects, you should not expect the return
+ * value to stay the same, and your code should continue to work even
+ * if the return value changes.
+ *
* @param offset The offset from which the range starts. Should be between 0 and
* {@link #dataSize()}.
* @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
@@ -921,6 +933,12 @@ public final class Parcel {
* <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method
* for that.
*
+ * WARNING: Parcelable definitions change over time. Unless you define
+ * a Parcelable yourself OR the Parcelable explicitly guarantees that
+ * it would never include such objects, you should not expect the return
+ * value to stay the same, and your code should continue to work even
+ * if the return value changes.
+ *
* @throws IllegalArgumentException if you provide any object not supported by above methods
* (including if the unsupported object is inside a nested container).
*
@@ -990,6 +1008,13 @@ public final class Parcel {
*
* @throws UnsupportedOperationException if binder kernel driver was disabled or if method was
* invoked in case of Binder RPC protocol.
+ *
+ * WARNING: Parcelable definitions change over time. Unless you define
+ * a Parcelable yourself OR the Parcelable explicitly guarantees that
+ * it would never include such objects, you should not expect the return
+ * value to stay the same, and your code should continue to work even
+ * if the return value changes.
+ *
* @hide
*/
public boolean hasBinders() {
@@ -1000,6 +1025,12 @@ public final class Parcel {
* Report whether the parcel contains any marshalled {@link IBinder} objects in the range
* defined by {@code offset} and {@code length}.
*
+ * WARNING: Parcelable definitions change over time. Unless you define
+ * a Parcelable yourself OR the Parcelable explicitly guarantees that
+ * it would never include such objects, you should not expect the return
+ * value to stay the same, and your code should continue to work even
+ * if the return value changes.
+ *
* @param offset The offset from which the range starts. Should be between 0 and
* {@link #dataSize()}.
* @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9e7bf47ae83f..cd48f0847f8d 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1266,9 +1266,17 @@ public final class PowerManager {
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public float getBrightnessConstraint(int constraint) {
+ public float getBrightnessConstraint(@BrightnessConstraint int constraint) {
+ return getBrightnessConstraint(Display.DEFAULT_DISPLAY, constraint);
+ }
+
+ /**
+ * Gets a float screen brightness setting for a specific display.
+ * @hide
+ */
+ public float getBrightnessConstraint(int displayId, @BrightnessConstraint int constraint) {
try {
- return mService.getBrightnessConstraint(constraint);
+ return mService.getBrightnessConstraint(displayId, constraint);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index f64a81177ce2..11c54ef802fe 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -193,4 +193,31 @@ public class SELinux {
return false;
}
}
+
+ /**
+ * Gets the genfs labels version of the vendor. The genfs labels version is
+ * specified in {@code /vendor/etc/selinux/genfs_labels_version.txt}. The
+ * version follows the VINTF version format "YYYYMM" and affects how {@code
+ * genfs_contexts} entries are applied.
+ *
+ * <p>The genfs labels version indicates changes in the SELinux labeling
+ * scheme over time. For example:
+ * <ul>
+ * <li>For version 202504 and later, {@code /sys/class/udc} is labeled as
+ * {@code sysfs_udc}.
+ * <li>For version 202404 and earlier, {@code /sys/class/udc} is labeled
+ * as {@code sysfs}.
+ * </ul>
+ * Check {@code /system/etc/selinux/plat_sepolicy_genfs_{version}.cil} to
+ * see which labels are new in {version}.
+ *
+ * <p>Older vendors may override {@code genfs_contexts} with vendor-specific
+ * extensions. The framework must not break such labellings to maintain
+ * compatibility with such vendors, by checking the genfs labels version and
+ * implementing a fallback mechanism.
+ *
+ * @return an integer representing the genfs labels version of /vendor, in
+ * the format YYYYMM.
+ */
+ public static final native int getGenfsLabelsVersion();
}
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 8aec7eb59e91..9085fe09bdaa 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -277,7 +277,8 @@ public final class ServiceManager {
if (service != null) {
return service;
} else {
- return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder());
+ return Binder.allowBlocking(
+ getIServiceManager().checkService(name).getServiceWithMetadata().service);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
@@ -425,7 +426,8 @@ public final class ServiceManager {
private static IBinder rawGetService(String name) throws RemoteException {
final long start = sStatLogger.getTime();
- final IBinder binder = getIServiceManager().getService2(name).getBinder();
+ final IBinder binder =
+ getIServiceManager().getService2(name).getServiceWithMetadata().service;
final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 5a9c8787ee3b..49b696d95723 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -61,7 +61,7 @@ class ServiceManagerProxy implements IServiceManager {
@UnsupportedAppUsage
public IBinder getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name).getBinder();
+ return checkService(name).getServiceWithMetadata().service;
}
public Service getService2(String name) throws RemoteException {
diff --git a/core/java/android/os/SessionCreationConfig.aidl b/core/java/android/os/SessionCreationConfig.aidl
index cdc0ef461e0c..17147e43cf83 100644
--- a/core/java/android/os/SessionCreationConfig.aidl
+++ b/core/java/android/os/SessionCreationConfig.aidl
@@ -36,4 +36,12 @@ parcelable SessionCreationConfig {
* List of the modes to be enabled upon session creation.
*/
SessionMode[] modesToEnable;
+
+ /**
+ * List of layers to attach this session to.
+ *
+ * Note: DO NOT STORE THESE IN HintSessionManager, as
+ * it will break the layer lifecycle.
+ */
+ IBinder[] layerTokens;
}
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 4b16c1dce463..6431f3ce73f3 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -14,6 +14,8 @@
package android.os;
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import android.util.ArraySet;
import java.util.concurrent.LinkedBlockingQueue;
@@ -93,9 +95,48 @@ public class TestLooperManager {
}
/**
- * Releases the looper to continue standard looping and processing of messages,
- * no further interactions with TestLooperManager will be allowed after
- * release() has been called.
+ * Returns the next message that should be executed by this queue, and removes it from the
+ * queue. If the queue is empty or no messages are deliverable, returns null.
+ * This method never blocks.
+ *
+ * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions
+ * with it have completed.
+ */
+ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
+ @Nullable
+ public Message pop() {
+ checkReleased();
+ return mQueue.popForTest();
+ }
+
+ /**
+ * Returns the values of {@link Message#when} of the next message that should be executed by
+ * this queue. If the queue is empty or no messages are deliverable, returns null.
+ * This method never blocks.
+ */
+ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
+ @SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value
+ @Nullable
+ public Long peekWhen() {
+ checkReleased();
+ return mQueue.peekWhenForTest();
+ }
+
+ /**
+ * Checks whether the Looper is currently blocked on a sync barrier.
+ *
+ * A Looper is blocked on a sync barrier if there is a Message in the Looper's
+ * queue that is ready for execution but is behind a sync barrier
+ */
+ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
+ public boolean isBlockedOnSyncBarrier() {
+ checkReleased();
+ return mQueue.isBlockedOnSyncBarrier();
+ }
+
+ /**
+ * Releases the looper to continue standard looping and processing of messages, no further
+ * interactions with TestLooperManager will be allowed after release() has been called.
*/
public void release() {
synchronized (sHeldLoopers) {
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 0a8f62fd56d8..81e4549c78d1 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -667,4 +668,23 @@ public class UpdateEngine {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Run postinstall script for specified partition |partition|
+ *
+ * @param partition The partition to trigger postinstall runs
+ *
+ * @throws ServiceSpecificException error code of this exception would be one of
+ * https://cs.android.com/android/platform/superproject/main/+/main:system/update_engine/common/error_code.h
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UPDATE_ENGINE_API)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public void triggerPostinstall(@NonNull String partition) {
+ try {
+ mUpdateEngine.triggerPostinstall(partition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a1ede5fee3f3..7e73a5d04866 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3846,7 +3846,7 @@ public class UserManager {
}
/**
- * Return the time when the context user was unlocked elapsed milliseconds since boot,
+ * Return the time when the calling user was unlocked elapsed milliseconds since boot,
* or 0 if not unlocked.
*
* @hide
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 3001fbd4fafa..2a467386569d 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -96,6 +96,7 @@ flag {
namespace: "crumpet"
description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
bug: "324046728"
+ is_exported: true
}
# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
@@ -178,6 +179,7 @@ flag {
namespace: "game"
description: "Feature flag for adding CPU/GPU headroom API"
bug: "346604998"
+ is_exported: true
}
flag {
@@ -223,6 +225,22 @@ flag {
}
flag {
+ name: "material_motion_tokens"
+ namespace: "systemui"
+ description: "Adding new Material Tokens for M3 Motion Spec"
+ bug: "324922198"
+ is_exported: true
+}
+
+flag {
+ name: "material_shape_tokens"
+ namespace: "systemui"
+ description: "Adding new Material Tokens for M3 Shape (corner radius) Spec"
+ bug: "324928718"
+ is_exported: true
+}
+
+flag {
name: "message_queue_tail_tracking"
namespace: "system_performance"
description: "track tail of message queue."
diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
index c45c51d15cc9..af56bfe50381 100644
--- a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
+++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
@@ -16,7 +16,7 @@
package android.os.instrumentation;
-import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IOffsetCallback;
import android.os.instrumentation.MethodDescriptor;
import android.os.instrumentation.TargetProcess;
@@ -28,6 +28,7 @@ import android.os.instrumentation.TargetProcess;
interface IDynamicInstrumentationManager {
/** Provides ART metadata about the described compiled method within the target process */
@PermissionManuallyEnforced
- @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
- in TargetProcess targetProcess, in MethodDescriptor methodDescriptor);
+ void getExecutableMethodFileOffsets(
+ in TargetProcess targetProcess, in MethodDescriptor methodDescriptor,
+ in IOffsetCallback callback);
}
diff --git a/core/java/android/os/instrumentation/IOffsetCallback.aidl b/core/java/android/os/instrumentation/IOffsetCallback.aidl
new file mode 100644
index 000000000000..a28c93f5353a
--- /dev/null
+++ b/core/java/android/os/instrumentation/IOffsetCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.os.instrumentation;
+
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+
+/**
+ * System private API for providing dynamic instrumentation offset results.
+ *
+ * {@hide}
+ */
+oneway interface IOffsetCallback {
+ void onResult(in @nullable ExecutableMethodFileOffsets offsets);
+}
diff --git a/core/java/android/os/instrumentation/MethodDescriptorParser.java b/core/java/android/os/instrumentation/MethodDescriptorParser.java
new file mode 100644
index 000000000000..57fc44ff623e
--- /dev/null
+++ b/core/java/android/os/instrumentation/MethodDescriptorParser.java
@@ -0,0 +1,82 @@
+/*
+ * 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.os.instrumentation;
+
+import android.annotation.NonNull;
+
+import java.lang.reflect.Method;
+
+/**
+ * A utility class for dynamic instrumentation / uprobestats.
+ *
+ * @hide
+ */
+public final class MethodDescriptorParser {
+
+ /**
+ * Parses a {@link MethodDescriptor} (in string representation) into a {@link Method}.
+ */
+ public static Method parseMethodDescriptor(ClassLoader classLoader,
+ @NonNull MethodDescriptor descriptor) {
+ try {
+ Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
+ Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length];
+ for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) {
+ String typeName = descriptor.fullyQualifiedParameters[i];
+ boolean isArrayType = typeName.endsWith("[]");
+ if (isArrayType) {
+ typeName = typeName.substring(0, typeName.length() - 2);
+ }
+ switch (typeName) {
+ case "boolean":
+ parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class;
+ break;
+ case "byte":
+ parameters[i] = isArrayType ? byte.class.arrayType() : byte.class;
+ break;
+ case "char":
+ parameters[i] = isArrayType ? char.class.arrayType() : char.class;
+ break;
+ case "short":
+ parameters[i] = isArrayType ? short.class.arrayType() : short.class;
+ break;
+ case "int":
+ parameters[i] = isArrayType ? int.class.arrayType() : int.class;
+ break;
+ case "long":
+ parameters[i] = isArrayType ? long.class.arrayType() : long.class;
+ break;
+ case "float":
+ parameters[i] = isArrayType ? float.class.arrayType() : float.class;
+ break;
+ case "double":
+ parameters[i] = isArrayType ? double.class.arrayType() : double.class;
+ break;
+ default:
+ parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType()
+ : classLoader.loadClass(typeName);
+ }
+ }
+
+ return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "The specified method cannot be found. Is this descriptor valid? "
+ + descriptor, e);
+ }
+ }
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e98397d104d6..2473de4ff6d7 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1795,14 +1795,45 @@ public final class PermissionManager {
}
}
- /** @hide */
- public static final String CACHE_KEY_PACKAGE_INFO =
+ // The legacy system property "package_info" had two purposes: to invalidate PIC caches and to
+ // signal that package information, and therefore permissions, might have changed.
+ // AudioSystem is the only client of the signaling behavior. The "separate permissions
+ // notification" feature splits the two behaviors into two system property names.
+ //
+ // If the feature is disabled (legacy behavior) then the two system property names have the
+ // same value. This means there is only one system property in use.
+ //
+ // If the feature is enabled, then the two system property names have different values, which
+ // means there is a system property used by PIC and a system property used for signaling. The
+ // legacy value is hard-coded in native code that relies on the signaling behavior, so the
+ // system property name for signaling is the legacy property name, and the system property
+ // name for PIC is new.
+ private static String getPackageInfoCacheKey() {
+ if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+ return PropertyInvalidatedCache.createSystemCacheKey("package_info_cache");
+ } else {
+ return CACHE_KEY_PACKAGE_INFO_NOTIFY;
+ }
+ }
+
+ /**
+ * The system property that is used to notify clients that package information, and therefore
+ * permissions, may have changed.
+ * @hide
+ */
+ public static final String CACHE_KEY_PACKAGE_INFO_NOTIFY =
PropertyInvalidatedCache.createSystemCacheKey("package_info");
+ /**
+ * The system property that is used to invalidate PIC caches.
+ * @hide
+ */
+ public static final String CACHE_KEY_PACKAGE_INFO_CACHE = getPackageInfoCacheKey();
+
/** @hide */
private static final PropertyInvalidatedCache<PermissionQuery, Integer> sPermissionCache =
new PropertyInvalidatedCache<PermissionQuery, Integer>(
- 2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") {
+ 2048, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPermission") {
@Override
public Integer recompute(PermissionQuery query) {
return checkPermissionUncached(query.permission, query.pid, query.uid,
@@ -1920,7 +1951,7 @@ public final class PermissionManager {
private static PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>
sPackageNamePermissionCache =
new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>(
- 16, CACHE_KEY_PACKAGE_INFO, "checkPackageNamePermission") {
+ 16, CACHE_KEY_PACKAGE_INFO_CACHE, "checkPackageNamePermission") {
@Override
public Integer recompute(PackageNamePermissionQuery query) {
return checkPackageNamePermissionUncached(
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index a7195834e6bb..2c4883f1f61c 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,6 +213,7 @@ flag {
namespace: "permissions"
description: "Enable getDeviceId API in OpEventProxyInfo"
bug: "337340961"
+ is_exported: true
}
flag {
@@ -254,6 +255,7 @@ flag {
namespace: "permissions"
description: "New setOnOpNotedCallback API to allow subscribing to only sync ops."
bug: "372910217"
+ is_exported: true
}
flag {
@@ -445,3 +447,48 @@ flag {
description: "Enable the Wallet role within profiles"
bug: "356107987"
}
+
+flag {
+ name: "text_classifier_choice_api_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "API change to enable getTextClassifier by type"
+ bug: "377229653"
+}
+
+flag {
+ name: "updatable_text_classifier_for_otp_detection_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables text classifier for OTP detection that is updatable from mainline module"
+ bug: "377229653"
+}
+
+flag {
+ name: "cross_user_role_platform_api_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable cross-user roles platform API"
+ bug: "367732307"
+}
+
+flag {
+ name: "rate_limit_batched_note_op_async_callbacks_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Rate limit async noteOp callbacks for batched noteOperation binder call"
+ bug: "366013082"
+}
+
+flag {
+ name: "system_vendor_intelligence_role_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "This flag is used to enable the role system_vendor_intelligence"
+ bug: "377553620"
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index f6bdc18c29ec..25e8a4ddffcd 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -16,6 +16,8 @@
package android.preference;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
+
import android.animation.LayoutTransition;
import android.annotation.Nullable;
import android.annotation.StringRes;
@@ -54,6 +56,8 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.util.XmlUtils;
@@ -209,6 +213,11 @@ public abstract class PreferenceActivity extends ListActivity implements
private int mPreferenceHeaderItemResId = 0;
private boolean mPreferenceHeaderRemoveEmptyIcon = false;
+ private boolean mIsBackCallbackRegistered = false;
+ private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked;
+ private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
+ this::updateBackCallbackRegistrationState;
+
/**
* The starting request code given out to preference framework.
*/
@@ -699,11 +708,36 @@ public abstract class PreferenceActivity extends ListActivity implements
skipButton.setVisibility(View.VISIBLE);
}
}
+ updateBackCallbackRegistrationState();
+ getFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}
@Override
public void onBackPressed() {
- if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
+ onBackInvoked();
+ }
+
+ private void updateBackCallbackRegistrationState() {
+ if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return;
+ if ((mCurHeader != null && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null
+ && mSinglePane) || getFragmentManager().getBackStackEntryCount() != 0) {
+ if (!mIsBackCallbackRegistered) {
+ getOnBackInvokedDispatcher()
+ .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = true;
+ }
+ } else if (mIsBackCallbackRegistered) {
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = false;
+ }
+ }
+
+ private void onBackInvoked() {
+ if (WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)
+ && getFragmentManager().getBackStackEntryCount() != 0) {
+ getFragmentManager().popBackStackImmediate();
+ } else if (mCurHeader != null && mSinglePane
+ && getFragmentManager().getBackStackEntryCount() == 0
&& getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
mCurHeader = null;
@@ -713,9 +747,10 @@ public abstract class PreferenceActivity extends ListActivity implements
showBreadCrumbs(mActivityTitle, null);
}
getListView().clearChoices();
- } else {
+ } else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
super.onBackPressed();
}
+ updateBackCallbackRegistrationState();
}
/**
@@ -989,6 +1024,7 @@ public abstract class PreferenceActivity extends ListActivity implements
@Override
protected void onDestroy() {
+ getFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
mHandler.removeMessages(MSG_BUILD_HEADERS);
super.onDestroy();
@@ -1221,6 +1257,7 @@ public abstract class PreferenceActivity extends ListActivity implements
getListView().clearChoices();
}
showBreadCrumbs(header);
+ updateBackCallbackRegistrationState();
}
void showBreadCrumbs(Header header) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d5b525884ac1..4acb6312f90d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -85,6 +85,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.service.voice.VisualQueryDetectedResult;
import android.speech.tts.TextToSpeech;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -2468,6 +2469,25 @@ public final class Settings {
= "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
/**
+ * Activity Action: Show the settings for users to select their preferred SIM subscription
+ * when a new SIM subscription has become available.
+ * <p>
+ * This Activity will only launch successfully if the newly active subscription ID is set as the
+ * value of {@link EXTRA_SUB_ID} and the value corresponds with an active SIM subscription.
+ * <p>
+ * Input: {@link #EXTRA_SUB_ID}: the subscription ID of the newly active SIM subscription.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_ACTION_SIM_PREFERENCE_SETTINGS)
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SIM_PREFERENCE_SETTINGS =
+ "android.settings.SIM_PREFERENCE_SETTINGS";
+
+ /**
* Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
* settings metrics that logs the entry point about physical keyboard settings.
* <p>
@@ -6289,6 +6309,13 @@ public final class Settings {
public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone";
/**
+ * Whether to enable system gestures (three- and four-finger swipes) on touchpads.
+ *
+ * @hide
+ */
+ public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
+
+ /**
* Whether to enable reversed vertical scrolling for connected mice.
*
* When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6374,6 +6401,14 @@ public final class Settings {
public static final String LOCALE_PREFERENCES = "locale_preferences";
/**
+ * User can change the region from region settings. This records user's preferred region.
+ *
+ * E.g. : if user's locale is en-US, this will record US
+ * @hide
+ */
+ public static final String PREFERRED_REGION = "preferred_region";
+
+ /**
* Setting to enable camera flash notification feature.
* <ul>
* <li> 0 = Off
@@ -6541,12 +6576,14 @@ public final class Settings {
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_TO_CLICK);
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
+ PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON);
+ PRIVATE_SETTINGS.add(PREFERRED_REGION);
}
/**
@@ -10005,6 +10042,12 @@ public final class Settings {
"minimal_post_processing_allowed";
/**
+ * Whether to mirror the built-in display on all connected displays.
+ * @hide
+ */
+ public static final String MIRROR_BUILT_IN_DISPLAY = "mirror_built_in_display";
+
+ /**
* No mode switching will happen.
*
* @see #MATCH_CONTENT_FRAME_RATE
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 0302fafd2f6c..59628e8e69d7 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,7 +16,10 @@
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;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -343,6 +346,28 @@ public final class AdvancedProtectionManager {
return intent;
}
+ /** @hide */
+ public @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction(
+ @NonNull String identifier, @Nullable @SupportDialogType String type) {
+ Objects.requireNonNull(identifier);
+ if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+ throw new IllegalArgumentException(type + " is not a valid type. See"
+ + " SUPPORT_DIALOG_TYPE_* APIs.");
+ }
+ final String featureId;
+ if (DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals(identifier)) {
+ 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)) {
+ featureId = FEATURE_ID_ENABLE_MTE;
+ } else {
+ throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
+ }
+ return createSupportIntent(featureId, type);
+ }
+
/**
* A callback class for monitoring changes to Advanced Protection state
*
diff --git a/core/java/android/security/advancedprotection/OWNERS b/core/java/android/security/advancedprotection/OWNERS
index ddac8edb6f4a..bfb7e16e15a8 100644
--- a/core/java/android/security/advancedprotection/OWNERS
+++ b/core/java/android/security/advancedprotection/OWNERS
@@ -2,7 +2,6 @@
achim@google.com
azharaa@google.com
-cpinelli@google.com
eranm@google.com
hanikazmi@google.com
haok@google.com
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 09004b3dcf03..34bae46b484c 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -112,6 +112,7 @@ flag {
namespace: "hardware_backed_security"
description: "AFL feature"
bug: "365994454"
+ is_exported: true
}
flag {
@@ -127,6 +128,7 @@ flag {
namespace: "hardware_backed_security"
description: "Feature flag for exposing KeyStore grant APIs"
bug: "351158708"
+ is_exported: true
}
flag {
@@ -134,4 +136,5 @@ flag {
namespace: "biometrics"
description: "Feature flag for Secure Lockdown feature"
bug: "373422357"
+ is_exported: true
} \ No newline at end of file
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 45cd0f011299..a46049fb2f6d 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -17,6 +17,7 @@
package android.security.net.config;
import android.util.ArraySet;
+
import java.security.cert.X509Certificate;
import java.util.Set;
@@ -24,16 +25,23 @@ import java.util.Set;
public final class CertificatesEntryRef {
private final CertificateSource mSource;
private final boolean mOverridesPins;
+ private final boolean mDisableCT;
- public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {
+ public CertificatesEntryRef(CertificateSource source, boolean overridesPins,
+ boolean disableCT) {
mSource = source;
mOverridesPins = overridesPins;
+ mDisableCT = disableCT;
}
boolean overridesPins() {
return mOverridesPins;
}
+ boolean disableCT() {
+ return mDisableCT;
+ }
+
public Set<TrustAnchor> getTrustAnchors() {
// TODO: cache this [but handle mutable sources]
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java
index 8d4f098bcb37..a54d8d0499cb 100644
--- a/core/java/android/security/net/config/KeyStoreConfigSource.java
+++ b/core/java/android/security/net/config/KeyStoreConfigSource.java
@@ -17,8 +17,8 @@
package android.security.net.config;
import android.util.Pair;
+
import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.util.Set;
/**
@@ -32,7 +32,7 @@ class KeyStoreConfigSource implements ConfigSource {
mConfig = new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
// Use the KeyStore and do not override pins (of which there are none).
- new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false))
+ new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false, false))
.build();
}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 129ae63ec9c0..410c68b8d04d 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -112,7 +112,6 @@ public final class NetworkSecurityConfig {
return mHstsEnforced;
}
- // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides.
public boolean isCertificateTransparencyVerificationRequired() {
return mCertificateTransparencyVerificationRequired;
}
@@ -192,20 +191,21 @@ public final class NetworkSecurityConfig {
* @hide
*/
public static Builder getDefaultBuilder(ApplicationInfo info) {
+ // System certificate store, does not bypass static pins, does not disable CT.
+ CertificatesEntryRef systemRef = new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false);
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
- // System certificate store, does not bypass static pins.
- .addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
+ .addCertificatesEntryRef(systemRef);
final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
&& !info.isInstantApp();
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
- // User certificate store, does not bypass static pins.
+ // User certificate store, does not bypass static pins. CT is disabled.
builder.addCertificatesEntryRef(
- new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
+ new CertificatesEntryRef(UserCertificateSource.getInstance(), false, true));
}
return builder;
}
@@ -339,6 +339,16 @@ public final class NetworkSecurityConfig {
if (mCertificateTransparencyVerificationRequiredSet) {
return mCertificateTransparencyVerificationRequired;
}
+ // CT verification has not been set explicitly. Before deferring to
+ // the parent, check if any of the CertificatesEntryRef requires it
+ // to be disabled (i.e., user store or inline certificate).
+ if (hasCertificatesEntryRefs()) {
+ for (CertificatesEntryRef ref : getCertificatesEntryRefs()) {
+ if (ref.disableCT()) {
+ return false;
+ }
+ }
+ }
if (mParentBuilder != null) {
return mParentBuilder.getCertificateTransparencyVerificationRequired();
}
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index b1c14793bbbd..95e579fc538b 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -182,6 +182,7 @@ public class XmlConfigSource implements ConfigSource {
boolean overridePins =
parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
int sourceId = parser.getAttributeResourceValue(null, "src", -1);
+ boolean disableCT = false;
String sourceString = parser.getAttributeValue(null, "src");
CertificateSource source = null;
if (sourceString == null) {
@@ -190,10 +191,12 @@ public class XmlConfigSource implements ConfigSource {
if (sourceId != -1) {
// TODO: Cache ResourceCertificateSources by sourceId
source = new ResourceCertificateSource(sourceId, mContext);
+ disableCT = true;
} else if ("system".equals(sourceString)) {
source = SystemCertificateSource.getInstance();
} else if ("user".equals(sourceString)) {
source = UserCertificateSource.getInstance();
+ disableCT = true;
} else if ("wfa".equals(sourceString)) {
source = WfaCertificateSource.getInstance();
} else {
@@ -201,7 +204,7 @@ public class XmlConfigSource implements ConfigSource {
+ "Should be one of system|user|@resourceVal");
}
XmlUtils.skipCurrentTag(parser);
- return new CertificatesEntryRef(source, overridePins);
+ return new CertificatesEntryRef(source, overridePins, disableCT);
}
private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser,
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index a5c837b88fa4..8add9f7c63cc 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -78,6 +78,7 @@ flag {
description: "Prevent intent redirect attacks"
bug: "361143368"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -96,6 +97,13 @@ flag {
}
flag {
+ name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected_r_w"
+ namespace: "responsible_apis"
+ description: "Prevent intent redirect attacks by showing a toast if not yet collected"
+ bug: "361143368"
+}
+
+flag {
name: "prevent_intent_redirect_throw_exception_if_nested_keys_not_collected"
namespace: "responsible_apis"
description: "Prevent intent redirect attacks by throwing exception if the intent does not collect nested keys"
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 269839b61bef..2d922b4c09ee 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -15,9 +15,12 @@
*/
package android.service.autofill;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_SESSION_DESTROYED;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -669,6 +672,14 @@ public abstract class AutofillService extends Service {
AutofillService.this,
new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS)));
}
+
+ @Override
+ public void onSessionDestroyed(@Nullable FillEventHistory history) {
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onSessionDestroyed,
+ AutofillService.this,
+ history));
+ }
};
private Handler mHandler;
@@ -783,26 +794,42 @@ public abstract class AutofillService extends Service {
}
/**
- * Gets the events that happened after the last
- * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+ * Called when an Autofill context has ended and the Autofill session is finished. This will be
+ * called as the last step of the Autofill lifecycle described in {@link AutofillManager}.
+ *
+ * <p>This will also contain the finished Session's FillEventHistory, so providers do not need
+ * to explicitly call {@link #getFillEventHistory()}
+ *
+ * <p>This will usually happens whenever {@link AutofillManager#commit()} or {@link
+ * AutofillManager#cancel()} is called.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_SESSION_DESTROYED)
+ public void onSessionDestroyed(@Nullable FillEventHistory history) {}
+
+ /**
+ * Gets the events that happened after the last {@link
+ * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
* call.
*
* <p>This method is typically used to keep track of previous user actions to optimize further
* requests. For example, the service might return email addresses in alphabetical order by
* default, but change that order based on the address the user picked on previous requests.
*
- * <p>The history is not persisted over reboots, and it's cleared every time the service
- * replies to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling
- * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)}
- * (if the service doesn't call any of these methods, the history will clear out after some
- * pre-defined time). Hence, the service should call {@link #getFillEventHistory()} before
- * finishing the {@link FillCallback}.
+ * <p>The history is not persisted over reboots, and it's cleared every time the service replies
+ * to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling {@link
+ * FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} (if the
+ * service doesn't call any of these methods, the history will clear out after some pre-defined
+ * time). Hence, the service should call {@link #getFillEventHistory()} before finishing the
+ * {@link FillCallback}.
*
* @return The history or {@code null} if there are no events.
- *
* @throws RuntimeException if the event history could not be retrieved.
+ * @deprecated Use {@link #onSessionDestroyed(FillEventHistory) instead}
*/
- @Nullable public final FillEventHistory getFillEventHistory() {
+ @FlaggedApi(FLAG_AUTOFILL_SESSION_DESTROYED)
+ @Deprecated
+ @Nullable
+ public final FillEventHistory getFillEventHistory() {
final AutofillManager afm = getSystemService(AutofillManager.class);
if (afm == null) {
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 14a14e69f208..6d62a2c7db2e 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -170,6 +170,9 @@ public final class FillEventHistory implements Parcelable {
}
parcel.writeInt(event.mSaveDialogNotShowReason);
parcel.writeInt(event.mUiType);
+ if (Flags.addLastFocusedIdToFillEventHistory()) {
+ parcel.writeParcelable(event.mFocusedId, 0);
+ }
}
}
}
@@ -338,7 +341,7 @@ public final class FillEventHistory implements Parcelable {
/** Credential Manager suggestions are shown instead of Autofill suggestion */
@FlaggedApi(FLAG_AUTOFILL_W_METRICS)
- public static final int UI_TYPE_CREDMAN = 4;
+ public static final int UI_TYPE_CREDENTIAL_MANAGER = 4;
/** @hide */
@IntDef(prefix = { "UI_TYPE_" }, value = {
@@ -375,6 +378,8 @@ public final class FillEventHistory implements Parcelable {
@UiType
private final int mUiType;
+ @Nullable private final AutofillId mFocusedId;
+
/**
* Returns the type of the event.
*
@@ -388,7 +393,7 @@ public final class FillEventHistory implements Parcelable {
@FlaggedApi(FLAG_AUTOFILL_W_METRICS)
@Nullable
public AutofillId getFocusedId() {
- return null;
+ return mFocusedId;
}
/**
@@ -624,6 +629,7 @@ public final class FillEventHistory implements Parcelable {
* @param manuallyFilledDatasetIds The ids of datasets that had values matching the
* respective entry on {@code manuallyFilledFieldIds}.
* @param detectedFieldClassifications the field classification matches.
+ * @param focusedId the field which was focused at the time of event trigger
*
* @throws IllegalArgumentException If the length of {@code changedFieldIds} and
* {@code changedDatasetIds} doesn't match.
@@ -640,11 +646,12 @@ public final class FillEventHistory implements Parcelable {
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
@Nullable AutofillId[] detectedFieldIds,
- @Nullable FieldClassification[] detectedFieldClassifications) {
+ @Nullable FieldClassification[] detectedFieldClassifications,
+ @Nullable AutofillId focusedId) {
this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds,
changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
- NO_SAVE_UI_REASON_NONE);
+ NO_SAVE_UI_REASON_NONE, focusedId);
}
/**
@@ -665,6 +672,7 @@ public final class FillEventHistory implements Parcelable {
* respective entry on {@code manuallyFilledFieldIds}.
* @param detectedFieldClassifications the field classification matches.
* @param saveDialogNotShowReason The reason why a save dialog was not shown.
+ * @param focusedId the field which was focused at the time of event trigger
*
* @throws IllegalArgumentException If the length of {@code changedFieldIds} and
* {@code changedDatasetIds} doesn't match.
@@ -682,11 +690,12 @@ public final class FillEventHistory implements Parcelable {
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
@Nullable AutofillId[] detectedFieldIds,
@Nullable FieldClassification[] detectedFieldClassifications,
- int saveDialogNotShowReason) {
+ int saveDialogNotShowReason,
+ @Nullable AutofillId focusedId) {
this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds,
changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, UI_TYPE_UNKNOWN);
+ saveDialogNotShowReason, UI_TYPE_UNKNOWN, focusedId);
}
/**
@@ -708,6 +717,7 @@ public final class FillEventHistory implements Parcelable {
* @param detectedFieldClassifications the field classification matches.
* @param saveDialogNotShowReason The reason why a save dialog was not shown.
* @param uiType The ui presentation type for fill suggestion.
+ * @param focusedId the field which was focused at the time of event trigger
*
* @throws IllegalArgumentException If the length of {@code changedFieldIds} and
* {@code changedDatasetIds} doesn't match.
@@ -725,7 +735,7 @@ public final class FillEventHistory implements Parcelable {
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
@Nullable AutofillId[] detectedFieldIds,
@Nullable FieldClassification[] detectedFieldClassifications,
- int saveDialogNotShowReason, int uiType) {
+ int saveDialogNotShowReason, int uiType, @Nullable AutofillId focusedId) {
mEventType = Preconditions.checkArgumentInRange(eventType, 0,
TYPE_VIEW_REQUESTED_AUTOFILL, "eventType");
mDatasetId = datasetId;
@@ -756,6 +766,7 @@ public final class FillEventHistory implements Parcelable {
NO_SAVE_UI_REASON_NONE, NO_SAVE_UI_REASON_DATASET_MATCH,
"saveDialogNotShowReason");
mUiType = uiType;
+ mFocusedId = focusedId;
}
@Override
@@ -852,13 +863,17 @@ public final class FillEventHistory implements Parcelable {
: null;
final int saveDialogNotShowReason = parcel.readInt();
final int uiType = parcel.readInt();
+ AutofillId focusedId = null;
+ if (Flags.addLastFocusedIdToFillEventHistory()) {
+ focusedId = parcel.readParcelable(null, AutofillId.class);
+ }
selection.addEvent(new Event(eventType, datasetId, clientState,
selectedDatasetIds, ignoredDatasets,
changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, uiType));
+ saveDialogNotShowReason, uiType, focusedId));
}
return selection;
}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 3b64b8a0ec5e..71b75e7789c2 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -25,6 +25,8 @@ import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
import com.android.internal.os.IResultReceiver;
+parcelable FillEventHistory;
+
/**
* Interface from the system to an auto fill service.
*
@@ -38,4 +40,5 @@ oneway interface IAutoFillService {
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
+ void onSessionDestroyed(in FillEventHistory history);
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 5d0ec73a024b..72569075c2ed 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2363,7 +2363,6 @@ public abstract class NotificationListenerService extends Service {
// -- parcelable interface --
private RankingMap(Parcel in) {
- final ClassLoader cl = getClass().getClassLoader();
final int count = in.readInt();
mOrderedKeys.ensureCapacity(count);
mRankings.ensureCapacity(count);
diff --git a/core/java/android/service/ondeviceintelligence/OWNERS b/core/java/android/service/ondeviceintelligence/OWNERS
deleted file mode 100644
index 09774f78d712..000000000000
--- a/core/java/android/service/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 7225f27c4555..9736345ae3a9 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
index 1acb7b8460cf..ea7d4a675713 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
@@ -187,27 +187,39 @@ public final class SettingsPreferenceMetadata implements Parcelable {
/** @hide */
@IntDef(value = {
- NOT_SENSITIVE,
- SENSITIVE,
- INTENT_ONLY
+ NO_SENSITIVITY,
+ EXPECT_POST_CONFIRMATION,
+ EXPECT_PRE_CONFIRMATION,
+ NO_DIRECT_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WriteSensitivity {}
/**
- * Preference is not sensitive, thus its value is writable without explicit consent, assuming
- * all necessary permissions are granted.
+ * Indicates preference is not sensitive.
+ * <p>Its value is writable without explicit consent, assuming all necessary permissions are
+ * granted.
*/
- public static final int NOT_SENSITIVE = 0;
+ public static final int NO_SENSITIVITY = 0;
/**
- * Preference is sensitive, meaning that in addition to necessary permissions, writing its value
- * will also request explicit user consent.
+ * Indicates preference is mildly sensitive.
+ * <p>In addition to necessary permissions, after writing its value the user should be
+ * given the option to revert back.
*/
- public static final int SENSITIVE = 1;
+ public static final int EXPECT_POST_CONFIRMATION = 1;
/**
- * Preference is not permitted for write-access via API and must be changed via Settings page.
+ * Indicates preference is sensitive.
+ * <p>In addition to necessary permissions, the user should be prompted for confirmation prior
+ * to making a change. Otherwise it is suggested to provide a deeplink to the Preference's page
+ * instead, accessible via {@link #getLaunchIntent}.
*/
- public static final int INTENT_ONLY = 2;
+ public static final int EXPECT_PRE_CONFIRMATION = 2;
+ /**
+ * Indicates preference is highly sensitivity and carries significant user-risk.
+ * <p>This Preference cannot be changed through this API and no direct deeplink is available.
+ * Other Metadata is still available.
+ */
+ public static final int NO_DIRECT_ACCESS = 3;
private SettingsPreferenceMetadata(@NonNull Builder builder) {
mKey = builder.mKey;
@@ -303,7 +315,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
private boolean mAvailable = false;
private boolean mWritable = false;
private boolean mRestricted = false;
- @WriteSensitivity private int mSensitivity = INTENT_ONLY;
+ @WriteSensitivity private int mSensitivity = NO_DIRECT_ACCESS;
private Intent mLaunchIntent;
private Bundle mExtras;
@@ -436,6 +448,9 @@ public final class SettingsPreferenceMetadata implements Parcelable {
*/
@NonNull
public SettingsPreferenceMetadata build() {
+ if (mSensitivity == NO_DIRECT_ACCESS) {
+ mLaunchIntent = null;
+ }
return new SettingsPreferenceMetadata(this);
}
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 7d79fd3d44ea..68fd11592fee 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1541,7 +1541,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
mInternalCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setKeyphrases(recognitionExtra)
.setData(data)
.setAudioCapabilities(audioCapabilities)
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 5f9f6bde3129..b6f0270dfbbc 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -1,6 +1,6 @@
# Bug component: 533220
-
include /core/java/android/app/assist/OWNERS
+atneya@google.com
# The owner here should not be assist owner
adudani@google.com
diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags
index e209a286966a..5ba2baec6fcf 100644
--- a/core/java/android/speech/tts/EventLogTags.logtags
+++ b/core/java/android/speech/tts/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.speech.tts;
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 2c585e640fdd..e8b32ce5e314 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -658,12 +658,15 @@ public class TelephonyCallback {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42;
/**
* Event for listening to changes in carrier roaming non-terrestrial network eligibility.
*
- * @see CarrierRoamingNtnModeListener
+ * @see CarrierRoamingNtnModeListener#onCarrierRoamingNtnEligibleStateChanged(boolean)
*
* Device is eligible for satellite communication if all the following conditions are met:
* <ul>
@@ -679,11 +682,15 @@ public class TelephonyCallback {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
/**
* Event for listening to changes in carrier roaming non-terrestrial network available services
- * via callback onCarrierRoamingNtnAvailableServicesChanged().
+ * via callback {@link
+ * CarrierRoamingNtnModeListener#onCarrierRoamingNtnAvailableServicesChanged(List)}
* This callback is triggered when the available services provided by the carrier roaming
* satellite changes. The carrier roaming satellite is defined by the following conditions.
* <ul>
@@ -693,15 +700,22 @@ public class TelephonyCallback {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
/**
* Event for listening to carrier roaming non-terrestrial network signal strength changes.
*
- * @see CarrierRoamingNtnModeListener
+ * @see CarrierRoamingNtnModeListener#onCarrierRoamingNtnSignalStrengthChanged(
+ * NtnSignalStrength)
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45;
/**
@@ -1803,6 +1817,8 @@ public class TelephonyCallback {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public interface CarrierRoamingNtnModeListener {
/**
* Callback invoked when carrier roaming non-terrestrial network mode changes.
@@ -1836,10 +1852,10 @@ public class TelephonyCallback {
* Callback invoked when carrier roaming non-terrestrial network available
* service changes.
*
- * @param availableServices The list of the supported services.
+ * @param availableServices array of supported services.
*/
default void onCarrierRoamingNtnAvailableServicesChanged(
- @NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
+ @NonNull @NetworkRegistrationInfo.ServiceType int[] availableServices) {}
/**
* Callback invoked when carrier roaming non-terrestrial network signal strength changes.
@@ -2343,10 +2359,8 @@ public class TelephonyCallback {
(CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
- List<Integer> ServiceList = Arrays.stream(availableServices).boxed()
- .collect(Collectors.toList());
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
+ () -> listener.onCarrierRoamingNtnAvailableServicesChanged(availableServices)));
}
public void onCarrierRoamingNtnSignalStrengthChanged(
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f43f172d7d5b..c2e542c57171 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -91,6 +91,7 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
@@ -196,6 +197,7 @@ flag {
namespace: "text"
description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
bug: "337103893"
+ is_exported: true
}
flag {
@@ -203,6 +205,7 @@ flag {
namespace: "text"
description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
bug: "349519475"
+ is_exported: true
}
flag {
@@ -210,4 +213,5 @@ flag {
namespace: "text"
description: "Make Paint class work for vertical layout text."
bug: "355296926"
+ is_exported: true
}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 1dd9d46fdfb7..f8737a55a5b0 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,7 +75,7 @@ import java.net.UnknownHostException;
@android.ravenwood.annotation.RavenwoodClassLoadHook(
"com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded")
// Uncomment the following annotation to switch to the Java substitution version.
-@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host")
+@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_ravenwood")
public final class Log {
/** @hide */
@IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index c9d560c3424b..802bddd76e30 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1301,17 +1301,23 @@ public final class Display {
}
/**
- * Represents the {@link FrameRateCategory} for the Normal frame rate
+ * Normal category determines the framework's recommended normal frame rate.
+ * Opt for this normal rate unless a higher frame rate significantly enhances
+ * the user experience.
*
- * @see FrameRateCategory
+ * @see #getSuggestedFrameRate(int)
+ * @see #FRAME_RATE_CATEGORY_HIGH
*/
@FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
public static final int FRAME_RATE_CATEGORY_NORMAL = 0;
/**
- * Represents the {@link FrameRateCategory} for the High frame rate
+ * High category determines the framework's recommended high frame rate.
+ * Opt for this high rate when a higher frame rate significantly enhances
+ * the user experience.
*
- * @see FrameRateCategory
+ * @see #getSuggestedFrameRate(int)
+ * @see #FRAME_RATE_CATEGORY_NORMAL
*/
@FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
public static final int FRAME_RATE_CATEGORY_HIGH = 1;
@@ -1332,22 +1338,17 @@ public final class Display {
*
* <p> For example, an animation that does not require fast render rates can use
* the {@link #FRAME_RATE_CATEGORY_NORMAL} to get the suggested frame rate.
- * The suggested frame rate then can be used in the
- * {@link Surface.FrameRateParams.Builder#setDesiredRateRange} for desiredMinRate.
*
* <pre>{@code
* float desiredMinRate = display.getSuggestedFrameRate(FRAME_RATE_CATEGORY_NORMAL);
- * Surface.FrameRateParams params = new Surface.FrameRateParams.Builder().
- * setDesiredRateRange(desiredMinRate, Float.MAX).build();
- * surface.setFrameRate(params);
+ * surface.setFrameRate(desiredMinRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
* }</pre>
* </p>
*
* @param category either {@link #FRAME_RATE_CATEGORY_NORMAL}
* or {@link #FRAME_RATE_CATEGORY_HIGH}
*
- * @see Surface#setFrameRate(Surface.FrameRateParams)
- * @see SurfaceControl.Transaction#setFrameRate(SurfaceControl, Surface.FrameRateParams)
+ * @see Surface#setFrameRate(float, int)
* @throws IllegalArgumentException when category is not {@link #FRAME_RATE_CATEGORY_NORMAL}
* or {@link #FRAME_RATE_CATEGORY_HIGH}
*/
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index bb233d2711de..3ff5f95cd71f 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -286,7 +286,7 @@ public abstract class DisplayEventReceiver {
* @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
* timebase.
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
- * @param modeId The new mode Id
+ * @param modeId The new mode ID
* @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
*/
public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
@@ -294,6 +294,15 @@ public abstract class DisplayEventReceiver {
}
/**
+ * Called when a display mode rejection event is received.
+ *
+ * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+ * @param modeId The mode ID of the mode that was rejected
+ */
+ public void onModeRejected(long physicalDisplayId, int modeId) {
+ }
+
+ /**
* Called when a display hdcp levels change event is received.
*
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
@@ -386,6 +395,12 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
+ private void dispatchModeRejected(long physicalDisplayId, int modeId) {
+ onModeRejected(physicalDisplayId, modeId);
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,
FrameRateOverride[] overrides) {
onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8b6458a54c43..43078847326c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -361,6 +361,12 @@ public final class DisplayInfo implements Parcelable {
public float brightnessDefault;
/**
+ * The current dim brightness of the display. Value between 0.0 and 1.0,
+ * derived from the configuration of the display device of this logical display.
+ */
+ public float brightnessDim;
+
+ /**
* The {@link RoundedCorners} if present, otherwise {@code null}.
*/
@Nullable
@@ -479,6 +485,7 @@ public final class DisplayInfo implements Parcelable {
&& brightnessMinimum == other.brightnessMinimum
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
+ && brightnessDim == other.brightnessDim
&& Objects.equals(roundedCorners, other.roundedCorners)
&& installOrientation == other.installOrientation
&& Objects.equals(displayShape, other.displayShape)
@@ -546,6 +553,7 @@ public final class DisplayInfo implements Parcelable {
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ brightnessDim = other.brightnessDim;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
displayShape = other.displayShape;
@@ -620,6 +628,7 @@ public final class DisplayInfo implements Parcelable {
brightnessMinimum = source.readFloat();
brightnessMaximum = source.readFloat();
brightnessDefault = source.readFloat();
+ brightnessDim = source.readFloat();
roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
int numUserDisabledFormats = source.readInt();
userDisabledHdrTypes = new int[numUserDisabledFormats];
@@ -696,6 +705,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeFloat(brightnessMinimum);
dest.writeFloat(brightnessMaximum);
dest.writeFloat(brightnessDefault);
+ dest.writeFloat(brightnessDim);
dest.writeTypedObject(roundedCorners, flags);
dest.writeInt(userDisabledHdrTypes.length);
for (int i = 0; i < userDisabledHdrTypes.length; i++) {
@@ -994,6 +1004,8 @@ public final class DisplayInfo implements Parcelable {
sb.append(brightnessMaximum);
sb.append(", brightnessDefault ");
sb.append(brightnessDefault);
+ sb.append(", brightnessDim ");
+ sb.append(brightnessDim);
sb.append(", installOrientation ");
sb.append(Surface.rotationToString(installOrientation));
sb.append(", layoutLimitedRefreshRate ");
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 45dbe43bbdd5..21b969c1dedb 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -60,5 +60,5 @@ oneway interface IDisplayWindowInsetsController {
* Reports the requested IME visibility of the IME input target to
* the IDisplayWindowInsetsController
*/
- void setImeInputTargetRequestedVisibility(boolean visible);
+ void setImeInputTargetRequestedVisibility(boolean visible, in ImeTracker.Token statsToken);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e5be53191e9e..5da49857dda5 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -62,6 +62,7 @@ import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
+import android.view.inputmethod.ImeTracker;
import android.view.AppTransitionAnimationSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
@@ -765,7 +766,8 @@ interface IWindowManager
* container.
*/
@EnforcePermission("MANAGE_APP_TOKENS")
- void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes);
+ void updateDisplayWindowRequestedVisibleTypes(int displayId, int visibleTypes, int mask,
+ in @nullable ImeTracker.Token statsToken);
/**
* Called to get the expected window insets.
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index b796e0b1c429..ba208390c8b7 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -357,6 +357,31 @@ public class InsetsSource implements Parcelable {
} else if (mTmpFrame.right == relativeFrame.right) {
return Insets.of(0, 0, mTmpFrame.width(), 0);
}
+ } else {
+ // The source doesn't cover the width or the height of relativeFrame, but just parts of
+ // them. Here uses mSideHint to decide which side should be inset.
+ switch (mSideHint) {
+ case SIDE_LEFT:
+ if (mTmpFrame.left == relativeFrame.left) {
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ }
+ break;
+ case SIDE_TOP:
+ if (mTmpFrame.top == relativeFrame.top) {
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ }
+ break;
+ case SIDE_RIGHT:
+ if (mTmpFrame.right == relativeFrame.right) {
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ }
+ break;
+ case SIDE_BOTTOM:
+ if (mTmpFrame.bottom == relativeFrame.bottom) {
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ break;
+ }
}
return Insets.NONE;
}
diff --git a/core/java/android/view/LetterboxScrollProcessor.java b/core/java/android/view/LetterboxScrollProcessor.java
index 1364a82e60a1..8c1b0f88f4eb 100644
--- a/core/java/android/view/LetterboxScrollProcessor.java
+++ b/core/java/android/view/LetterboxScrollProcessor.java
@@ -78,6 +78,11 @@ public class LetterboxScrollProcessor {
@Nullable
@VisibleForTesting(visibility = PACKAGE)
public List<MotionEvent> processMotionEvent(@NonNull MotionEvent motionEvent) {
+ if (!motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ // This is a non-pointer event that doesn't correspond to any location on the screen.
+ // Ignore it.
+ return null;
+ }
mProcessedEvents.clear();
final Rect appBounds = getAppBounds();
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 03f9d9814b43..6e6e87bb9403 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -205,7 +205,8 @@ public class Surface implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
- value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+ value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ FRAME_RATE_COMPATIBILITY_GTE})
public @interface FrameRateCompatibility {}
// From native_window.h. Keep these in sync.
@@ -214,6 +215,11 @@ public class Surface implements Parcelable {
* system selects a frame rate other than what the app requested, the app will be able
* to run at the system frame rate without requiring pull down. This value should be
* used when displaying game content, UIs, and anything that isn't video.
+ *
+ * In Android version {@link Build.VERSION_CODES#BAKLAVA} and above, use
+ * {@link FRAME_RATE_COMPATIBILITY_DEFAULT} for game content.
+ * For other cases, see {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} and
+ * {@link FRAME_RATE_COMPATIBILITY_GTE}.
*/
public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
@@ -228,6 +234,17 @@ public class Surface implements Parcelable {
public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
/**
+ * The surface requests a frame rate that is greater than or equal to the specified frame rate.
+ * This value should be used for UIs, animations, scrolling and fling, and anything that is not
+ * a game or video.
+ *
+ * For video, use {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} instead. For game content, use
+ * {@link FRAME_RATE_COMPATIBILITY_DEFAULT}.
+ */
+ @FlaggedApi(com.android.graphics.surfaceflinger.flags.Flags.FLAG_ARR_SETFRAMERATE_GTE_ENUM)
+ public static final int FRAME_RATE_COMPATIBILITY_GTE = 2;
+
+ /**
* This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
* to operate at the exact frame rate.
*
@@ -250,13 +267,6 @@ public class Surface implements Parcelable {
*/
public static final int FRAME_RATE_COMPATIBILITY_MIN = 102;
- // From window.h. Keep these in sync.
- /**
- * The surface requests a frame rate that is greater than or equal to {@code frameRate}.
- * @hide
- */
- public static final int FRAME_RATE_COMPATIBILITY_GTE = 103;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CHANGE_FRAME_RATE_"},
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 049189f8af8d..0681745a0fc6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28277,14 +28277,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
- if (mParent != null && !mParent.isLayoutRequested()) {
- mParent.requestLayout();
+ if (mParent != null) {
+ if (!mParent.isLayoutRequested()) {
+ mParent.requestLayout();
+ } else {
+ clearMeasureCacheOfAncestors();
+ }
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
+ private void clearMeasureCacheOfAncestors() {
+ ViewParent parent = mParent;
+ while (parent instanceof View view) {
+ if (view.mMeasureCache != null) {
+ view.mMeasureCache.clear();
+ }
+ parent = view.mParent;
+ }
+ }
+
/**
* Forces this view to be laid out during the next layout pass.
* This method does not call requestLayout() or forceLayout()
@@ -28640,8 +28654,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@RemotableViewMethod
public void setMinimumHeight(int minHeight) {
- mMinHeight = minHeight;
- requestLayout();
+ if (mMinHeight != minHeight) {
+ mMinHeight = minHeight;
+ requestLayout();
+ }
}
/**
@@ -28671,8 +28687,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@RemotableViewMethod
public void setMinimumWidth(int minWidth) {
- mMinWidth = minWidth;
- requestLayout();
+ if (mMinWidth != minWidth) {
+ mMinWidth = minWidth;
+ requestLayout();
+ }
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 63bf392b5ef1..9e97a8eb58aa 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -591,7 +591,7 @@ public class ViewConfiguration {
res.getBoolean(
com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
mViewTouchScreenHapticScrollFeedbackEnabled =
- Flags.enableTouchScrollFeedback()
+ Flags.enableScrollFeedbackForTouch()
? res.getBoolean(
com.android.internal.R.bool
.config_viewTouchScreenHapticScrollFeedbackEnabled)
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a0feccd87a81..1596b85bb461 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.adpf.Flags.adpfViewrootimplActionDownBoost;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
@@ -1208,6 +1209,8 @@ public final class ViewRootImpl implements ViewParent,
private long mRenderThreadDrawStartTimeNs = -1;
private long mFirstFramePresentedTimeNs = -1;
+ private final boolean mSendPerfHintOnTouch;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1337,6 +1340,8 @@ public final class ViewRootImpl implements ViewParent,
CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
mIsSubscribeGranularDisplayEventsEnabled =
com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
+
+ mSendPerfHintOnTouch = adpfViewrootimplActionDownBoost();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -2249,7 +2254,7 @@ public final class ViewRootImpl implements ViewParent,
onClientWindowFramesChanged(frames);
- CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
+ CompatibilityInfo.applyOverrideIfNeeded(mergedConfiguration);
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
final Rect attachedFrame = frames.attachedFrame;
@@ -7110,6 +7115,10 @@ public final class ViewRootImpl implements ViewParent,
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+ if (inTouchMode && mAttachInfo.mThreadedRenderer != null && mSendPerfHintOnTouch) {
+ mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+ }
+
// tell the window manager
try {
IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
@@ -9449,7 +9458,7 @@ public final class ViewRootImpl implements ViewParent,
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
}
mInvCompatScale = 1f / mTmpFrames.compatScale;
- CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
+ CompatibilityInfo.applyOverrideIfNeeded(mPendingMergedConfiguration);
handleInsetsControlChanged(mTempInsets, mTempControls);
mPendingAlwaysConsumeSystemBars =
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1be7f4849f07..43a946a234ba 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -87,8 +87,12 @@ public abstract class ViewStructure {
* <p>This value is added to mainly help with debugging purpose.
*/
@FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @SuppressWarnings(
+ "ActionValue") // Lint expects this as
+ // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER
+ // but should not have contentcapture
public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
- "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+ "android.view.extra.VIRTUAL_STRUCTURE_TYPE";
/**
* Key used for specifying the version of the view that generated the virtual structure for
@@ -98,8 +102,12 @@ public abstract class ViewStructure {
* "104.0.5112.69", then the value should be "104.0.5112.69"
*/
@FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @SuppressWarnings(
+ "ActionValue") // Lint expects this as
+ // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE
+ // but should not have contentcapture
public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
- "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+ "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
/**
* Set the identifier for this view.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1e8cad61381c..101d5c950b71 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -80,6 +80,9 @@ import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS;
import static android.view.WindowLayoutParamsProto.X;
import static android.view.WindowLayoutParamsProto.Y;
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
+
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -4465,6 +4468,29 @@ public interface WindowManager extends ViewManager {
public static final int INPUT_FEATURE_SENSITIVE_FOR_PRIVACY = 1 << 3;
/**
+ * Input feature used to indicate that the system should send power key events to this
+ * window when it's in the foreground. The window can override the double press power key
+ * gesture behavior.
+ *
+ * A double press gesture is defined as two
+ * {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)} events within a time span defined by
+ * {@link ViewConfiguration#getMultiPressTimeout()}.
+ *
+ * Note: While the window may receive all power key {@link KeyEvent}s, it can only
+ * override the double press gesture behavior. The system will perform default behavior for
+ * single, long-press and other multi-press gestures, regardless of if the app handles the
+ * key or not.
+ *
+ * To override the default behavior for double press, the app must return true for the
+ * second {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)}. If the app returns false, the
+ * system behavior will be performed for double press.
+ * @hide
+ */
+ @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public static final int
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS = 1 << 4;
+
+ /**
* An internal annotation for flags that can be specified to {@link #inputFeatures}.
*
* NOTE: These are not the same as {@link android.os.InputConfig} flags.
@@ -4477,6 +4503,7 @@ public interface WindowManager extends ViewManager {
INPUT_FEATURE_DISABLE_USER_ACTIVITY,
INPUT_FEATURE_SPY,
INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS
})
public @interface InputFeatureFlags {
}
@@ -4766,6 +4793,44 @@ public interface WindowManager extends ViewManager {
}
/**
+ * Specifies if the system should send power key events to this window when it's in the
+ * foreground, with only the double tap gesture behavior being overrideable.
+ *
+ * @param enabled if true, the system should send power key events to this window when it's
+ * in the foreground, with only the power key double tap gesture being
+ * overrideable.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void setReceivePowerKeyDoublePressEnabled(boolean enabled) {
+ if (enabled) {
+ inputFeatures
+ |= INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+ } else {
+ inputFeatures
+ &= ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+ }
+ }
+
+ /**
+ * Returns whether or not the system should send power key events to this window when it's
+ * in the foreground, with only the double tap gesture being overrideable.
+ *
+ * @return if the system should send power key events to this window when it's in the
+ * foreground, with only the double tap gesture being overrideable.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public boolean isReceivePowerKeyDoublePressEnabled() {
+ return (inputFeatures
+ & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
+ }
+
+ /**
* Specifies that the window should be considered a trusted system overlay. Trusted system
* overlays are ignored when considering whether windows are obscured during input
* dispatch. Requires the {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
@@ -6157,6 +6222,16 @@ public interface WindowManager extends ViewManager {
inputFeatures &= ~INPUT_FEATURE_SPY;
features.add("INPUT_FEATURE_SPY");
}
+ if (overridePowerKeyBehaviorInFocusedWindow()) {
+ if ((inputFeatures
+ & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
+ != 0) {
+ inputFeatures
+ &=
+ ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+ features.add("INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS");
+ }
+ }
if (inputFeatures != 0) {
features.add(Integer.toHexString(inputFeatures));
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 330e46af6381..97cf8fc748e8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -85,7 +85,7 @@ import java.util.function.IntConsumer;
* @see WindowManagerGlobal
* @hide
*/
-public final class WindowManagerImpl implements WindowManager {
+public class WindowManagerImpl implements WindowManager {
private static final String TAG = "WindowManager";
@UnsupportedAppUsage
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index df0c5a34e992..8a10979eb3c9 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -6540,6 +6540,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* Class with information if a node is a range.
*/
public static final class RangeInfo {
+ /** @hide */
+ @IntDef(prefix = { "RANGE_TYPE_" }, value = {
+ RANGE_TYPE_INT,
+ RANGE_TYPE_FLOAT,
+ RANGE_TYPE_PERCENT,
+ RANGE_TYPE_INDETERMINATE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangeType {}
/** Range type: integer. */
public static final int RANGE_TYPE_INT = 0;
@@ -6588,7 +6597,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param current The current value.
*/
@Deprecated
- public static RangeInfo obtain(int type, float min, float max, float current) {
+ public static RangeInfo obtain(@RangeType int type, float min, float max, float current) {
return new RangeInfo(type, min, max, current);
}
@@ -6602,7 +6611,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* maximum.
* @param current The current value.
*/
- public RangeInfo(int type, float min, float max, float current) {
+ public RangeInfo(@RangeType int type, float min, float max, float current) {
mType = type;
mMin = min;
mMax = max;
@@ -6618,6 +6627,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @see #RANGE_TYPE_FLOAT
* @see #RANGE_TYPE_PERCENT
*/
+ @RangeType
public int getType() {
return mType;
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e60fc3ae6b47..049ad20fd992 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -8,6 +8,7 @@ flag {
namespace: "accessibility"
description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
bug: "362782536"
+ is_exported: true
}
flag {
@@ -15,6 +16,7 @@ flag {
namespace: "accessibility"
description: "Adds an API to indicate whether a form field (or similar element) is required."
bug: "362784403"
+ is_exported: true
}
flag {
@@ -44,6 +46,7 @@ flag {
namespace: "accessibility"
description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
bug: "375429616"
+ is_exported: true
}
flag {
@@ -88,6 +91,7 @@ flag {
name: "deprecate_accessibility_announcement_apis"
description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
bug: "376727542"
+ is_exported: true
}
flag {
@@ -95,6 +99,7 @@ flag {
name: "deprecate_ani_label_for_apis"
description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
bug: "333783827"
+ is_exported: true
}
flag {
@@ -145,6 +150,7 @@ flag {
name: "global_action_menu"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -152,6 +158,7 @@ flag {
name: "global_action_media_play_pause"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -230,6 +237,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for supplemental description api"
bug: "375266174"
+ is_exported: true
}
flag {
@@ -237,6 +245,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
bug: "333780959"
+ is_exported: true
}
flag {
@@ -251,6 +260,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for adding tri-state checked api"
bug: "333784774"
+ is_exported: true
}
flag {
@@ -261,11 +271,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
- }
+}
- flag {
+flag {
name: "indeterminate_range_info"
namespace: "accessibility"
description: "Creates a way to create an INDETERMINATE RangeInfo"
bug: "376108874"
- }
+ is_exported: true
+}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 905f350ca6c5..d527007e586e 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -603,7 +603,7 @@ public class AutofillFeatureFlags {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELAYOUT,
- false);
+ true);
}
/** @hide */
@@ -611,7 +611,7 @@ public class AutofillFeatureFlags {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT,
- false);
+ true);
}
/** @hide **/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 52c5af8889ec..1de0474182dd 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+import static android.service.autofill.Flags.relayoutFix;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
@@ -1013,7 +1014,7 @@ public final class AutofillManager {
AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
mRelayoutFixDeprecated = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
- mRelayoutFix = AutofillFeatureFlags.enableRelayoutFixes();
+ mRelayoutFix = relayoutFix() && AutofillFeatureFlags.enableRelayoutFixes();
mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 1c7570efc26b..675e5a1b4804 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -127,6 +127,7 @@ flag {
description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
bug: "335874198"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/view/flags/scroll_capture.aconfig b/core/java/android/view/flags/scroll_capture.aconfig
index fdf9c1ed8ad2..9080b1669ed5 100644
--- a/core/java/android/view/flags/scroll_capture.aconfig
+++ b/core/java/android/view/flags/scroll_capture.aconfig
@@ -3,7 +3,7 @@ container: "system"
flag {
name: "scroll_capture_target_z_order_fix"
- namespace: "system_ui"
+ namespace: "systemui"
description: "Always prefer targets with higher z-order"
bug: "365969802"
metadata {
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index ebda4d472b0d..ddf6ff11a83a 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -17,10 +17,10 @@ flag {
}
flag {
- namespace: "toolkit"
- name: "enable_touch_scroll_feedback"
+ namespace: "wear_frameworks"
+ name: "enable_scroll_feedback_for_touch"
description: "Enables touchscreen haptic scroll feedback"
- bug: "331830899"
+ bug: "382135785"
is_fixed_read_only: true
}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 3b6343e7c4ae..f6fdec94c332 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -116,6 +116,7 @@ flag {
description: "Add a SurfaceView composition order control API."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -124,6 +125,7 @@ flag {
description: "Add APIs to manage SurfacePackage of the parent SurfaceView."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -140,4 +142,12 @@ flag {
description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
bug: "294922229"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "date_time_view_relative_time_display_configs"
+ namespace: "systemui"
+ description: "Enables DateTimeView to use additional display configurations for relative time"
+ bug: "364653005"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index dd32d57bd650..4d354e0655f0 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -222,6 +222,8 @@ public interface ImeTracker {
PHASE_CLIENT_ALREADY_HIDDEN,
PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
+ PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE,
+ PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -436,6 +438,12 @@ public interface ImeTracker {
* app or the RemoteInsetsControlTarget).
*/
int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
+ /** DisplayImeController received the requested visibility for the IME and stored it. */
+ int PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE =
+ ImeProtoEnums.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE;
+ /** The control target reported its requestedVisibleTypes back to WindowManagerService. */
+ int PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES =
+ ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES;
/**
* Called when an IME request is started.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6303c7637a59..6d89f3d89077 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -946,11 +946,16 @@ public final class InputMethodManager {
if (state == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
// when losing focus (e.g., by going to another window), we reset the
// requestedVisibleTypes of WindowInsetsController by hiding the IME
+ final var statsToken = ImeTracker.forLogging().onStart(
+ ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT,
+ SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS,
+ false /* fromUser */);
if (DEBUG) {
Log.d(TAG, "onWindowLostFocus, hiding IME because "
+ "of STATE_ALWAYS_HIDDEN");
}
- mCurRootView.getInsetsController().hide(WindowInsets.Type.ime());
+ mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
}
}
@@ -2626,10 +2631,12 @@ public final class InputMethodManager {
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Hiding soft input: reschedule to view thread");
+ final var finalStatsToken = statsToken;
vh.post(() -> viewRootImpl.getInsetsController().hide(
- WindowInsets.Type.ime()));
+ WindowInsets.Type.ime(), false /* fromIme */, finalStatsToken));
} else {
- viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime());
+ viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime(),
+ false /* fromIme */, statsToken);
}
}
return true;
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 73abc472be6d..41567fbf8b51 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,6 +165,7 @@ flag {
description: "Writing tools API"
bug: "373788889"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -191,4 +192,5 @@ flag {
description: "Verify KeyEvents in IME"
bug: "331730488"
is_fixed_read_only: true
+ is_exported: true
}
diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags
index a90aebd71716..8bbd5a9d0246 100644
--- a/core/java/android/webkit/EventLogTags.logtags
+++ b/core/java/android/webkit/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.webkit;
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index b18dbbc21cda..1bc952ca6546 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
@@ -36,8 +35,6 @@ public class UserPackage {
private final UserHandle mUser;
private final PackageInfo mPackageInfo;
- public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU;
-
public UserPackage(@NonNull UserHandle user, @Nullable PackageInfo packageInfo) {
mUser = user;
mPackageInfo = packageInfo;
@@ -83,14 +80,6 @@ public class UserPackage {
& ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
}
- /**
- * Returns whether the package represented by {@param packageInfo} targets a sdk version
- * supported by the current framework version.
- */
- public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
- return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
- }
-
public UserHandle getUser() {
return mUser;
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index de303f80ff9e..1a48bbb77d60 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -51,12 +51,6 @@ import java.lang.reflect.Method;
*/
@SystemApi
public final class WebViewFactory {
-
- // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
- /** @hide */
- private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webview.chromium.WebViewChromiumFactoryProviderForT";
-
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
private static final String LOGTAG = "WebViewFactory";
@@ -275,8 +269,8 @@ public final class WebViewFactory {
*/
public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
throws ClassNotFoundException {
- return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
- true, clazzLoader);
+ return (Class<WebViewFactoryProvider>) Class.forName(
+ WebViewFactoryProvider.getWebViewFactoryClassName(), true, clazzLoader);
}
/**
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 3d6450632e06..4a2f9ba696a5 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -20,8 +20,11 @@ import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.net.Network;
import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
import java.util.List;
@@ -34,6 +37,61 @@ import java.util.List;
@SystemApi
public interface WebViewFactoryProvider {
/**
+ * Used as the requirement when Flags.useBEntryPoint() is false.
+ * @hide
+ */
+ int MINIMUM_SUPPORTED_TARGET_SDK = Build.VERSION_CODES.TIRAMISU;
+
+ /**
+ * Used as the requirement when Flags.useBEntryPoint() is true.
+ * TODO: set to the actual minimum required version code - this is just the
+ * version shipped in V.
+ * @hide
+ */
+ long MINIMUM_SUPPORTED_VERSION_CODE = 661308800L;
+
+ /**
+ * Returns whether the WebView implementation represented by {@code packageInfo}
+ * is compatible with this version of Android.
+ * @hide
+ */
+ static boolean isCompatibleImplementationPackage(@NonNull PackageInfo packageInfo) {
+ if (Flags.useBEntryPoint()) {
+ return packageInfo.versionCode >= MINIMUM_SUPPORTED_VERSION_CODE;
+ } else {
+ return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_TARGET_SDK;
+ }
+ }
+
+ /**
+ * Returns a string describing the minimum requirement for a WebView implementation
+ * to be compatible with this version of Android, for debugging purposes.
+ * @hide
+ */
+ static @NonNull String describeCompatibleImplementationPackage() {
+ if (Flags.useBEntryPoint()) {
+ return TextUtils.formatSimple("Minimum versionCode for OS support: %d",
+ MINIMUM_SUPPORTED_VERSION_CODE);
+ } else {
+ return TextUtils.formatSimple("Minimum targetSdkVersion: %d",
+ MINIMUM_SUPPORTED_TARGET_SDK);
+ }
+ }
+
+ /**
+ * Returns the name of the class that should be used when loading the
+ * WebView implementation on this version of Android.
+ * @hide
+ */
+ static @NonNull String getWebViewFactoryClassName() {
+ if (Flags.useBEntryPoint()) {
+ return "com.android.webview.chromium.WebViewChromiumFactoryProviderForB";
+ } else {
+ return "com.android.webview.chromium.WebViewChromiumFactoryProviderForT";
+ }
+ }
+
+ /**
* This Interface provides glue for implementing the backend of WebView static methods which
* cannot be implemented in-situ in the proxy class.
*/
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index c9e94d2f57f6..c5176a2f1f15 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -42,3 +42,11 @@ flag {
description: "New APIs required by File System Access"
bug: "40101963"
}
+
+flag {
+ name: "use_b_entry_point"
+ namespace: "webview"
+ description: "Use B-specific entry point to WebView APK"
+ bug: "373617389"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3c854ea4fad4..0721fd379e9b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,7 +16,7 @@
package android.widget;
-import static android.view.flags.Flags.enableTouchScrollFeedback;
+import static android.view.flags.Flags.enableScrollFeedbackForTouch;
import static android.view.flags.Flags.scrollFeedbackApi;
import static android.view.flags.Flags.viewVelocityApi;
@@ -3737,7 +3737,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
// TODO: b/360198915 - Add unit testing for using ScrollFeedbackProvider
- if (enableTouchScrollFeedback()) {
+ if (enableScrollFeedbackForTouch()) {
initHapticScrollFeedbackProviderIfNotExists();
mHapticScrollFeedbackProvider.onScrollProgress(
vtev.getDeviceId(), vtev.getSource(), MotionEvent.AXIS_Y,
@@ -3779,7 +3779,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_OVERSCROLL;
}
- if (enableTouchScrollFeedback()) {
+ if (enableScrollFeedbackForTouch()) {
initHapticScrollFeedbackProviderIfNotExists();
mHapticScrollFeedbackProvider.onScrollLimit(
vtev.getDeviceId(), vtev.getSource(),
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 0bf6380eb904..eb3b76873a8f 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -134,6 +134,7 @@ public class Button extends TextView {
// 1. app target sdk is 36 or above.
// 2. feature flag rolled-out.
// 3. device is a watch.
+ // 4. button uses Theme.DeviceDefault.
// getButtonDefaultStyleAttr and getButtonDefaultStyleRes works together to alter the UI
// while considering the conditions above.
// Their results are mutual exclusive. i.e. when conditions above are all true,
@@ -229,6 +230,7 @@ public class Button extends TextView {
private static boolean useWearMaterial3Style(Context context) {
return Flags.useWearMaterial3Ui() && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_BUTTON)
- && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault;
}
}
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 41ff69d6fb5f..143b4b770984 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -21,6 +21,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+import android.annotation.IntDef;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -41,6 +42,8 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.DateFormat;
import java.time.Instant;
import java.time.LocalDate;
@@ -70,6 +73,23 @@ public class DateTimeView extends TextView {
private static final int SHOW_TIME = 0;
private static final int SHOW_MONTH_DAY_YEAR = 1;
+ /** @hide */
+ @IntDef(value = {UNIT_DISPLAY_LENGTH_SHORTEST, UNIT_DISPLAY_LENGTH_MEDIUM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnitDisplayLength {}
+ public static final int UNIT_DISPLAY_LENGTH_SHORTEST = 0;
+ public static final int UNIT_DISPLAY_LENGTH_MEDIUM = 1;
+
+ /** @hide */
+ @IntDef(flag = true, value = {DISAMBIGUATION_TEXT_PAST, DISAMBIGUATION_TEXT_FUTURE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisambiguationTextMask {}
+ public static final int DISAMBIGUATION_TEXT_PAST = 0x01;
+ public static final int DISAMBIGUATION_TEXT_FUTURE = 0x02;
+
+ private final boolean mCanUseRelativeTimeDisplayConfigs =
+ android.view.flags.Flags.dateTimeViewRelativeTimeDisplayConfigs();
+
private long mTimeMillis;
// The LocalDateTime equivalent of mTimeMillis but truncated to minute, i.e. no seconds / nanos.
private LocalDateTime mLocalTime;
@@ -81,6 +101,8 @@ public class DateTimeView extends TextView {
private static final ThreadLocal<ReceiverInfo> sReceiverInfo = new ThreadLocal<ReceiverInfo>();
private String mNowText;
private boolean mShowRelativeTime;
+ private int mRelativeTimeDisambiguationTextMask;
+ private int mRelativeTimeUnitDisplayLength = UNIT_DISPLAY_LENGTH_SHORTEST;
public DateTimeView(Context context) {
this(context, null);
@@ -89,20 +111,23 @@ public class DateTimeView extends TextView {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public DateTimeView(Context context, AttributeSet attrs) {
super(context, attrs);
- final TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.DateTimeView, 0,
- 0);
-
- final int N = a.getIndexCount();
- for (int i = 0; i < N; i++) {
- int attr = a.getIndex(i);
- switch (attr) {
- case R.styleable.DateTimeView_showRelative:
- boolean relative = a.getBoolean(i, false);
- setShowRelativeTime(relative);
- break;
- }
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.DateTimeView, 0, 0);
+
+ setShowRelativeTime(a.getBoolean(R.styleable.DateTimeView_showRelative, false));
+ if (mCanUseRelativeTimeDisplayConfigs) {
+ setRelativeTimeDisambiguationTextMask(
+ a.getInt(
+ R.styleable.DateTimeView_relativeTimeDisambiguationText,
+ // The original implementation showed disambiguation text for future
+ // times only, so continue with that default.
+ DISAMBIGUATION_TEXT_FUTURE));
+ setRelativeTimeUnitDisplayLength(
+ a.getInt(
+ R.styleable.DateTimeView_relativeTimeUnitDisplayLength,
+ UNIT_DISPLAY_LENGTH_SHORTEST));
}
+
a.recycle();
}
@@ -150,6 +175,29 @@ public class DateTimeView extends TextView {
update();
}
+ /** See {@link R.styleable.DateTimeView_relativeTimeDisambiguationText}. */
+ @android.view.RemotableViewMethod
+ public void setRelativeTimeDisambiguationTextMask(
+ @DisambiguationTextMask int disambiguationTextMask) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return;
+ }
+ mRelativeTimeDisambiguationTextMask = disambiguationTextMask;
+ updateNowText();
+ update();
+ }
+
+ /** See {@link R.styleable.DateTimeView_relativeTimeUnitDisplayLength}. */
+ @android.view.RemotableViewMethod
+ public void setRelativeTimeUnitDisplayLength(@UnitDisplayLength int unitDisplayLength) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return;
+ }
+ mRelativeTimeUnitDisplayLength = unitDisplayLength;
+ updateNowText();
+ update();
+ }
+
/**
* Returns whether this view shows relative time
*
@@ -264,17 +312,11 @@ public class DateTimeView extends TextView {
return;
} else if (duration < HOUR_IN_MILLIS) {
count = (int)(duration / MINUTE_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_minutes_shortest
- : com.android.internal.R.string.duration_minutes_shortest_future,
- count);
+ result = getContext().getResources().getString(getMinutesStringId(past), count);
millisIncrease = MINUTE_IN_MILLIS;
} else if (duration < DAY_IN_MILLIS) {
count = (int)(duration / HOUR_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_hours_shortest
- : com.android.internal.R.string.duration_hours_shortest_future,
- count);
+ result = getContext().getResources().getString(getHoursStringId(past), count);
millisIncrease = HOUR_IN_MILLIS;
} else if (duration < YEAR_IN_MILLIS) {
// In weird cases it can become 0 because of daylight savings
@@ -283,10 +325,7 @@ public class DateTimeView extends TextView {
LocalDateTime localNow = toLocalDateTime(now, zoneId);
count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_days_shortest
- : com.android.internal.R.string.duration_days_shortest_future,
- count);
+ result = getContext().getResources().getString(getDaysStringId(past), count);
if (past || count != 1) {
mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
millisIncrease = -1;
@@ -296,10 +335,7 @@ public class DateTimeView extends TextView {
} else {
count = (int)(duration / YEAR_IN_MILLIS);
- result = getContext().getResources().getString(past
- ? com.android.internal.R.string.duration_years_shortest
- : com.android.internal.R.string.duration_years_shortest_future,
- count);
+ result = getContext().getResources().getString(getYearsStringId(past), count);
millisIncrease = YEAR_IN_MILLIS;
}
if (millisIncrease != -1) {
@@ -312,6 +348,139 @@ public class DateTimeView extends TextView {
maybeSetText(result);
}
+ private int getMinutesStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_minutes_shortest
+ : com.android.internal.R.string.duration_minutes_shortest_future;
+ }
+
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1m ago"
+ return com.android.internal.R.string.duration_minutes_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1m"
+ return com.android.internal.R.string.duration_minutes_shortest_future;
+ } else {
+ // "1m"
+ return com.android.internal.R.string.duration_minutes_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1min ago"
+ return com.android.internal.R.string.duration_minutes_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1min"
+ return com.android.internal.R.string.duration_minutes_medium_future;
+ } else {
+ // "1min"
+ return com.android.internal.R.string.duration_minutes_medium;
+ }
+ }
+ }
+
+ private int getHoursStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_hours_shortest
+ : com.android.internal.R.string.duration_hours_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1h ago"
+ return com.android.internal.R.string.duration_hours_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1h"
+ return com.android.internal.R.string.duration_hours_shortest_future;
+ } else {
+ // "1h"
+ return com.android.internal.R.string.duration_hours_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1hr ago"
+ return com.android.internal.R.string.duration_hours_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1hr"
+ return com.android.internal.R.string.duration_hours_medium_future;
+ } else {
+ // "1hr"
+ return com.android.internal.R.string.duration_hours_medium;
+ }
+ }
+ }
+
+ private int getDaysStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_days_shortest
+ : com.android.internal.R.string.duration_days_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1d ago"
+ return com.android.internal.R.string.duration_days_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1d"
+ return com.android.internal.R.string.duration_days_shortest_future;
+ } else {
+ // "1d"
+ return com.android.internal.R.string.duration_days_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1d ago"
+ return com.android.internal.R.string.duration_days_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1d"
+ return com.android.internal.R.string.duration_days_medium_future;
+ } else {
+ // "1d"
+ return com.android.internal.R.string.duration_days_medium;
+ }
+ }
+ }
+
+ private int getYearsStringId(boolean past) {
+ if (!mCanUseRelativeTimeDisplayConfigs) {
+ return past
+ ? com.android.internal.R.string.duration_years_shortest
+ : com.android.internal.R.string.duration_years_shortest_future;
+ }
+ if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1y ago"
+ return com.android.internal.R.string.duration_years_shortest_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1y"
+ return com.android.internal.R.string.duration_years_shortest_future;
+ } else {
+ // "1y"
+ return com.android.internal.R.string.duration_years_shortest;
+ }
+ } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+ if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+ // "1y ago"
+ return com.android.internal.R.string.duration_years_medium_past;
+ } else if (!past
+ && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+ // "in 1y"
+ return com.android.internal.R.string.duration_years_medium_future;
+ } else {
+ // "1y"
+ return com.android.internal.R.string.duration_years_medium;
+ }
+ }
+ }
+
/**
* Sets text only if the text has actually changed. This prevents needles relayouts of this
* view when set to wrap_content.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7e3b90444429..2cd390113040 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -128,6 +128,7 @@ import com.android.internal.R;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.IRemoteViewsFactory;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
import com.android.internal.widget.remotecompose.player.RemoteComposePlayer;
@@ -5825,7 +5826,7 @@ public class RemoteViews implements Parcelable, Filter {
}
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
player.setDocument(new RemoteComposeDocument(is));
- player.addClickListener((viewId, metadata) -> {
+ player.addIdActionListener((viewId, metadata) -> {
mActions.forEach(action -> {
if (viewId == action.mViewId
&& action instanceof SetOnClickResponse setOnClickResponse) {
@@ -9829,7 +9830,7 @@ public class RemoteViews implements Parcelable, Filter {
*/
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
public static long getSupportedVersion() {
- return VERSION;
+ return (long) CoreDocument.getDocumentApiLevel();
}
/**
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 511c832a4876..184933fb8288 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,7 +16,7 @@
package android.widget;
-import static android.view.flags.Flags.enableTouchScrollFeedback;
+import static android.view.flags.Flags.enableScrollFeedbackForTouch;
import static android.view.flags.Flags.viewVelocityApi;
import android.annotation.ColorInt;
@@ -909,7 +909,7 @@ public class ScrollView extends FrameLayout {
}
// TODO: b/360198915 - Add unit tests.
- if (enableTouchScrollFeedback()) {
+ if (enableScrollFeedbackForTouch()) {
if (hitTopLimit || hitBottomLimit) {
initHapticScrollFeedbackProviderIfNotExists();
mHapticScrollFeedbackProvider.onScrollLimit(vtev.getDeviceId(),
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cb70466fcd81..7ad80886493c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -18,6 +18,7 @@ package android.widget;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.graphics.Paint.NEW_FONT_VARIATION_MANAGEMENT;
import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.ContentInfo.SOURCE_CLIPBOARD;
@@ -106,7 +107,6 @@ import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.os.Process;
import android.os.SystemClock;
-import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
@@ -5543,7 +5543,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
&& fontVariationSettings.equals(existingSettings))) {
return true;
}
- boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
+
+ final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
+ && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
+ boolean effective;
+ if (useFontVariationStore) {
+ if (mFontWeightAdjustment != 0
+ && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+ mTextPaint.setFontVariationSettings(fontVariationSettings, mFontWeightAdjustment);
+ } else {
+ mTextPaint.setFontVariationSettings(fontVariationSettings);
+ }
+ effective = true;
+ } else {
+ effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
+ }
if (effective && mLayout != null) {
nullLayouts();
@@ -9230,179 +9244,174 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onDraw(Canvas canvas) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onDraw");
- try {
- restartMarqueeIfNeeded();
-
- // Draw the background for this view
- super.onDraw(canvas);
-
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingTop = getCompoundPaddingTop();
- final int compoundPaddingRight = getCompoundPaddingRight();
- final int compoundPaddingBottom = getCompoundPaddingBottom();
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- final int right = mRight;
- final int left = mLeft;
- final int bottom = mBottom;
- final int top = mTop;
- final boolean isLayoutRtl = isLayoutRtl();
- final int offset = getHorizontalOffsetForDrawables();
- final int leftOffset = isLayoutRtl ? 0 : offset;
- final int rightOffset = isLayoutRtl ? offset : 0;
-
- final Drawables dr = mDrawables;
- if (dr != null) {
- /*
- * Compound, not extended, because the icon is not clipped
- * if the text height is smaller.
- */
+ restartMarqueeIfNeeded();
- int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
- int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
-
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.LEFT] != null) {
- canvas.save();
- canvas.translate(scrollX + mPaddingLeft + leftOffset,
- scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
- dr.mShowing[Drawables.LEFT].draw(canvas);
- canvas.restore();
- }
+ // Draw the background for this view
+ super.onDraw(canvas);
+
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingTop = getCompoundPaddingTop();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+ final int compoundPaddingBottom = getCompoundPaddingBottom();
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ final int right = mRight;
+ final int left = mLeft;
+ final int bottom = mBottom;
+ final int top = mTop;
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int offset = getHorizontalOffsetForDrawables();
+ final int leftOffset = isLayoutRtl ? 0 : offset;
+ final int rightOffset = isLayoutRtl ? offset : 0;
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.RIGHT] != null) {
- canvas.save();
- canvas.translate(scrollX + right - left - mPaddingRight
- - dr.mDrawableSizeRight - rightOffset,
- scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
- dr.mShowing[Drawables.RIGHT].draw(canvas);
- canvas.restore();
- }
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ /*
+ * Compound, not extended, because the icon is not clipped
+ * if the text height is smaller.
+ */
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.TOP] != null) {
- canvas.save();
- canvas.translate(scrollX + compoundPaddingLeft
- + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
- dr.mShowing[Drawables.TOP].draw(canvas);
- canvas.restore();
- }
+ int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
+ int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
- // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
- // Make sure to update invalidateDrawable() when changing this code.
- if (dr.mShowing[Drawables.BOTTOM] != null) {
- canvas.save();
- canvas.translate(scrollX + compoundPaddingLeft
- + (hspace - dr.mDrawableWidthBottom) / 2,
- scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
- dr.mShowing[Drawables.BOTTOM].draw(canvas);
- canvas.restore();
- }
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.LEFT] != null) {
+ canvas.save();
+ canvas.translate(scrollX + mPaddingLeft + leftOffset,
+ scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
+ dr.mShowing[Drawables.LEFT].draw(canvas);
+ canvas.restore();
}
- int color = mCurTextColor;
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.RIGHT] != null) {
+ canvas.save();
+ canvas.translate(scrollX + right - left - mPaddingRight
+ - dr.mDrawableSizeRight - rightOffset,
+ scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+ dr.mShowing[Drawables.RIGHT].draw(canvas);
+ canvas.restore();
+ }
- if (mLayout == null) {
- assumeLayout();
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.TOP] != null) {
+ canvas.save();
+ canvas.translate(scrollX + compoundPaddingLeft
+ + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
+ dr.mShowing[Drawables.TOP].draw(canvas);
+ canvas.restore();
}
- Layout layout = mLayout;
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
+ canvas.save();
+ canvas.translate(scrollX + compoundPaddingLeft
+ + (hspace - dr.mDrawableWidthBottom) / 2,
+ scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+ dr.mShowing[Drawables.BOTTOM].draw(canvas);
+ canvas.restore();
+ }
+ }
- if (mHint != null && !mHideHint && mText.length() == 0) {
- if (mHintTextColor != null) {
- color = mCurHintTextColor;
- }
+ int color = mCurTextColor;
- layout = mHintLayout;
- }
+ if (mLayout == null) {
+ assumeLayout();
+ }
- mTextPaint.setColor(color);
- mTextPaint.drawableState = getDrawableState();
+ Layout layout = mLayout;
- canvas.save();
- /* Would be faster if we didn't have to do this. Can we chop the
- (displayable) text so that we don't need to do this ever?
- */
+ if (mHint != null && !mHideHint && mText.length() == 0) {
+ if (mHintTextColor != null) {
+ color = mCurHintTextColor;
+ }
- int extendedPaddingTop = getExtendedPaddingTop();
- int extendedPaddingBottom = getExtendedPaddingBottom();
+ layout = mHintLayout;
+ }
- final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
- final int maxScrollY = mLayout.getHeight() - vspace;
+ mTextPaint.setColor(color);
+ mTextPaint.drawableState = getDrawableState();
- float clipLeft = compoundPaddingLeft + scrollX;
- float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
- float clipRight = right - left - getCompoundPaddingRight() + scrollX;
- float clipBottom = bottom - top + scrollY
- - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
+ canvas.save();
+ /* Would be faster if we didn't have to do this. Can we chop the
+ (displayable) text so that we don't need to do this ever?
+ */
- if (mShadowRadius != 0) {
- clipLeft += Math.min(0, mShadowDx - mShadowRadius);
- clipRight += Math.max(0, mShadowDx + mShadowRadius);
+ int extendedPaddingTop = getExtendedPaddingTop();
+ int extendedPaddingBottom = getExtendedPaddingBottom();
- clipTop += Math.min(0, mShadowDy - mShadowRadius);
- clipBottom += Math.max(0, mShadowDy + mShadowRadius);
- }
+ final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+ final int maxScrollY = mLayout.getHeight() - vspace;
- canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
+ float clipLeft = compoundPaddingLeft + scrollX;
+ float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
+ float clipRight = right - left - getCompoundPaddingRight() + scrollX;
+ float clipBottom = bottom - top + scrollY
+ - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
- int voffsetText = 0;
- int voffsetCursor = 0;
+ if (mShadowRadius != 0) {
+ clipLeft += Math.min(0, mShadowDx - mShadowRadius);
+ clipRight += Math.max(0, mShadowDx + mShadowRadius);
- // translate in by our padding
- /* shortcircuit calling getVerticaOffset() */
- if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
- voffsetText = getVerticalOffset(false);
- voffsetCursor = getVerticalOffset(true);
- }
- canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
-
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
- if (isMarqueeFadeEnabled()) {
- if (!mSingleLine && getLineCount() == 1 && canMarquee()
- && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
- final int width = mRight - mLeft;
- final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
- final float dx = mLayout.getLineRight(0) - (width - padding);
- canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
- }
+ clipTop += Math.min(0, mShadowDy - mShadowRadius);
+ clipBottom += Math.max(0, mShadowDy + mShadowRadius);
+ }
- if (mMarquee != null && mMarquee.isRunning()) {
- final float dx = -mMarquee.getScroll();
- canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
- }
- }
+ canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
- final int cursorOffsetVertical = voffsetCursor - voffsetText;
+ int voffsetText = 0;
+ int voffsetCursor = 0;
- maybeUpdateHighlightPaths();
- // If there is a gesture preview highlight, then the selection or cursor is not drawn.
- Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
- if (mEditor != null) {
- mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
- mHighlightPaint, cursorOffsetVertical);
- } else {
- layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
- cursorOffsetVertical);
+ // translate in by our padding
+ /* shortcircuit calling getVerticaOffset() */
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ voffsetText = getVerticalOffset(false);
+ voffsetCursor = getVerticalOffset(true);
+ }
+ canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+ if (isMarqueeFadeEnabled()) {
+ if (!mSingleLine && getLineCount() == 1 && canMarquee()
+ && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+ final int width = mRight - mLeft;
+ final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
+ final float dx = mLayout.getLineRight(0) - (width - padding);
+ canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
- if (mMarquee != null && mMarquee.shouldDrawGhost()) {
- final float dx = mMarquee.getGhostOffset();
+ if (mMarquee != null && mMarquee.isRunning()) {
+ final float dx = -mMarquee.getScroll();
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
- layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
- cursorOffsetVertical);
}
+ }
+
+ final int cursorOffsetVertical = voffsetCursor - voffsetText;
- canvas.restore();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ maybeUpdateHighlightPaths();
+ // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+ Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
+ if (mEditor != null) {
+ mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
+ mHighlightPaint, cursorOffsetVertical);
+ } else {
+ layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+ cursorOffsetVertical);
}
+
+ if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+ final float dx = mMarquee.getGhostOffset();
+ canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+ layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+ cursorOffsetVertical);
+ }
+
+ canvas.restore();
}
@Override
@@ -11260,201 +11269,192 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onMeasure");
- try {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- int width;
- int height;
+ int width;
+ int height;
- BoringLayout.Metrics boring = UNKNOWN_BORING;
- BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
+ BoringLayout.Metrics boring = UNKNOWN_BORING;
+ BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
- if (mTextDir == null) {
- mTextDir = getTextDirectionHeuristic();
+ if (mTextDir == null) {
+ mTextDir = getTextDirectionHeuristic();
+ }
+
+ int des = -1;
+ boolean fromexisting = false;
+ final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
+ ? (float) widthSize : Float.MAX_VALUE;
+
+ if (widthMode == MeasureSpec.EXACTLY) {
+ // Parent has told us how big to be. So be it.
+ width = widthSize;
+ } else {
+ if (mLayout != null && mEllipsize == null) {
+ des = desired(mLayout, mUseBoundsForWidth);
}
- int des = -1;
- boolean fromexisting = false;
- final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
- ? (float) widthSize : Float.MAX_VALUE;
+ if (des < 0) {
+ boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
+ isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
+ mBoring);
+ if (boring != null) {
+ mBoring = boring;
+ }
+ } else {
+ fromexisting = true;
+ }
- if (widthMode == MeasureSpec.EXACTLY) {
- // Parent has told us how big to be. So be it.
- width = widthSize;
+ if (boring == null || boring == UNKNOWN_BORING) {
+ if (des < 0) {
+ des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
+ mTransformed.length(), mTextPaint, mTextDir, widthLimit,
+ mUseBoundsForWidth));
+ }
+ width = des;
} else {
- if (mLayout != null && mEllipsize == null) {
- des = desired(mLayout, mUseBoundsForWidth);
+ if (mUseBoundsForWidth) {
+ RectF bbox = boring.getDrawingBoundingBox();
+ float rightMax = Math.max(bbox.right, boring.width);
+ float leftMin = Math.min(bbox.left, 0);
+ width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
+ } else {
+ width = boring.width;
}
+ }
- if (des < 0) {
- boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ width = Math.max(width, dr.mDrawableWidthTop);
+ width = Math.max(width, dr.mDrawableWidthBottom);
+ }
+
+ if (mHint != null) {
+ int hintDes = -1;
+ int hintWidth;
+
+ if (mHintLayout != null && mEllipsize == null) {
+ hintDes = desired(mHintLayout, mUseBoundsForWidth);
+ }
+
+ if (hintDes < 0) {
+ hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
- mBoring);
- if (boring != null) {
- mBoring = boring;
+ mHintBoring);
+ if (hintBoring != null) {
+ mHintBoring = hintBoring;
}
- } else {
- fromexisting = true;
}
- if (boring == null || boring == UNKNOWN_BORING) {
- if (des < 0) {
- des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
- mTransformed.length(), mTextPaint, mTextDir, widthLimit,
+ if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
+ if (hintDes < 0) {
+ hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
+ mHint.length(), mTextPaint, mTextDir, widthLimit,
mUseBoundsForWidth));
}
- width = des;
+ hintWidth = hintDes;
} else {
- if (mUseBoundsForWidth) {
- RectF bbox = boring.getDrawingBoundingBox();
- float rightMax = Math.max(bbox.right, boring.width);
- float leftMin = Math.min(bbox.left, 0);
- width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
- } else {
- width = boring.width;
- }
+ hintWidth = hintBoring.width;
}
- final Drawables dr = mDrawables;
- if (dr != null) {
- width = Math.max(width, dr.mDrawableWidthTop);
- width = Math.max(width, dr.mDrawableWidthBottom);
+ if (hintWidth > width) {
+ width = hintWidth;
}
+ }
- if (mHint != null) {
- int hintDes = -1;
- int hintWidth;
-
- if (mHintLayout != null && mEllipsize == null) {
- hintDes = desired(mHintLayout, mUseBoundsForWidth);
- }
+ width += getCompoundPaddingLeft() + getCompoundPaddingRight();
- if (hintDes < 0) {
- hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(),
- getResolvedMinimumFontMetrics(),
- mHintBoring);
- if (hintBoring != null) {
- mHintBoring = hintBoring;
- }
- }
-
- if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
- if (hintDes < 0) {
- hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
- mHint.length(), mTextPaint, mTextDir, widthLimit,
- mUseBoundsForWidth));
- }
- hintWidth = hintDes;
- } else {
- hintWidth = hintBoring.width;
- }
+ if (mMaxWidthMode == EMS) {
+ width = Math.min(width, mMaxWidth * getLineHeight());
+ } else {
+ width = Math.min(width, mMaxWidth);
+ }
- if (hintWidth > width) {
- width = hintWidth;
- }
- }
+ if (mMinWidthMode == EMS) {
+ width = Math.max(width, mMinWidth * getLineHeight());
+ } else {
+ width = Math.max(width, mMinWidth);
+ }
- width += getCompoundPaddingLeft() + getCompoundPaddingRight();
+ // Check against our minimum width
+ width = Math.max(width, getSuggestedMinimumWidth());
- if (mMaxWidthMode == EMS) {
- width = Math.min(width, mMaxWidth * getLineHeight());
- } else {
- width = Math.min(width, mMaxWidth);
- }
+ if (widthMode == MeasureSpec.AT_MOST) {
+ width = Math.min(widthSize, width);
+ }
+ }
- if (mMinWidthMode == EMS) {
- width = Math.max(width, mMinWidth * getLineHeight());
- } else {
- width = Math.max(width, mMinWidth);
- }
+ int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
+ int unpaddedWidth = want;
- // Check against our minimum width
- width = Math.max(width, getSuggestedMinimumWidth());
+ if (mHorizontallyScrolling) want = VERY_WIDE;
- if (widthMode == MeasureSpec.AT_MOST) {
- width = Math.min(widthSize, width);
- }
- }
+ int hintWant = want;
+ int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
- int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
- int unpaddedWidth = want;
+ if (mLayout == null) {
+ makeNewLayout(want, hintWant, boring, hintBoring,
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+ } else {
+ final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
+ || (mLayout.getEllipsizedWidth()
+ != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
- if (mHorizontallyScrolling) want = VERY_WIDE;
+ final boolean widthChanged = (mHint == null) && (mEllipsize == null)
+ && (want > mLayout.getWidth())
+ && (mLayout instanceof BoringLayout
+ || (fromexisting && des >= 0 && des <= want));
- int hintWant = want;
- int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
+ final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
- if (mLayout == null) {
- makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
- } else {
- final boolean layoutChanged =
- (mLayout.getWidth() != want) || (hintWidth != hintWant)
- || (mLayout.getEllipsizedWidth()
- != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
-
- final boolean widthChanged = (mHint == null) && (mEllipsize == null)
- && (want > mLayout.getWidth())
- && (mLayout instanceof BoringLayout
- || (fromexisting && des >= 0 && des <= want));
-
- final boolean maximumChanged =
- (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
-
- if (layoutChanged || maximumChanged) {
- if (!maximumChanged && widthChanged) {
- mLayout.increaseWidthTo(want);
- } else {
- makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
- false);
- }
+ if (layoutChanged || maximumChanged) {
+ if (!maximumChanged && widthChanged) {
+ mLayout.increaseWidthTo(want);
} else {
- // Nothing has changed
+ makeNewLayout(want, hintWant, boring, hintBoring,
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
}
- }
-
- if (heightMode == MeasureSpec.EXACTLY) {
- // Parent has told us how big to be. So be it.
- height = heightSize;
- mDesiredHeightAtMeasure = -1;
} else {
- int desired = getDesiredHeight();
+ // Nothing has changed
+ }
+ }
- height = desired;
- mDesiredHeightAtMeasure = desired;
+ if (heightMode == MeasureSpec.EXACTLY) {
+ // Parent has told us how big to be. So be it.
+ height = heightSize;
+ mDesiredHeightAtMeasure = -1;
+ } else {
+ int desired = getDesiredHeight();
- if (heightMode == MeasureSpec.AT_MOST) {
- height = Math.min(desired, heightSize);
- }
- }
+ height = desired;
+ mDesiredHeightAtMeasure = desired;
- int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
- if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
- unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
+ if (heightMode == MeasureSpec.AT_MOST) {
+ height = Math.min(desired, heightSize);
}
+ }
- /*
- * We didn't let makeNewLayout() register to bring the cursor into view,
- * so do it here if there is any possibility that it is needed.
- */
- if (mMovement != null
- || mLayout.getWidth() > unpaddedWidth
- || mLayout.getHeight() > unpaddedHeight) {
- registerForPreDraw();
- } else {
- scrollTo(0, 0);
- }
+ int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
+ if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
+ unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
+ }
- setMeasuredDimension(width, height);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ /*
+ * We didn't let makeNewLayout() register to bring the cursor into view,
+ * so do it here if there is any possibility that it is needed.
+ */
+ if (mMovement != null
+ || mLayout.getWidth() > unpaddedWidth
+ || mLayout.getHeight() > unpaddedHeight) {
+ registerForPreDraw();
+ } else {
+ scrollTo(0, 0);
}
+
+ setMeasuredDimension(width, height);
}
/**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7a01ad340c56..289c5cf4bf85 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -68,7 +68,8 @@ public enum DesktopModeFlags {
Flags::enableDesktopWindowingTaskbarRunningApps, true),
// TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up
ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
- ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(
+ Flags::enableDesktopWindowingEnterTransitions, false),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
Flags::enableWindowingTransitionHandlersObservers, false),
@@ -77,7 +78,15 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
Flags::enableDesktopAppLaunchTransitions, false),
ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
- ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true);
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingEnterTransitionBugfix, false),
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingExitTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchTransitionsBugfix, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/KeyguardState.java b/core/java/android/window/KeyguardState.java
index 6584d30cdaed..159275abb9ef 100644
--- a/core/java/android/window/KeyguardState.java
+++ b/core/java/android/window/KeyguardState.java
@@ -30,28 +30,23 @@ import java.util.Objects;
*/
public final class KeyguardState implements Parcelable {
- private final int mDisplayId;
-
private final boolean mKeyguardShowing;
private final boolean mAodShowing;
- private KeyguardState(int displayId, boolean keyguardShowing, boolean aodShowing) {
- mDisplayId = displayId;
+ private KeyguardState(boolean keyguardShowing, boolean aodShowing) {
mKeyguardShowing = keyguardShowing;
mAodShowing = aodShowing;
}
private KeyguardState(Parcel in) {
- mDisplayId = in.readInt();
mKeyguardShowing = in.readBoolean();
mAodShowing = in.readBoolean();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mDisplayId);
dest.writeBoolean(mKeyguardShowing);
dest.writeBoolean(mAodShowing);
}
@@ -70,13 +65,6 @@ public final class KeyguardState implements Parcelable {
}
};
- /**
- * Gets the display id of this {@link KeyguardState}.
- */
- public int getDisplayId() {
- return mDisplayId;
- }
-
/** Returns the keyguard showing value. */
public boolean getKeyguardShowing() {
return mKeyguardShowing;
@@ -89,15 +77,14 @@ public final class KeyguardState implements Parcelable {
@Override
public String toString() {
- return "KeyguardState{ displayId=" + mDisplayId
- + ", keyguardShowing=" + mKeyguardShowing
+ return "KeyguardState{ keyguardShowing=" + mKeyguardShowing
+ ", aodShowing=" + mAodShowing
+ '}';
}
@Override
public int hashCode() {
- return Objects.hash(mDisplayId, mKeyguardShowing, mAodShowing);
+ return Objects.hash(mKeyguardShowing, mAodShowing);
}
@Override
@@ -105,8 +92,7 @@ public final class KeyguardState implements Parcelable {
if (!(obj instanceof KeyguardState other)) {
return false;
}
- return mDisplayId == other.mDisplayId
- && mKeyguardShowing == other.mKeyguardShowing
+ return mKeyguardShowing == other.mKeyguardShowing
&& mAodShowing == other.mAodShowing;
}
@@ -117,18 +103,11 @@ public final class KeyguardState implements Parcelable {
/** Builder to construct the {@link KeyguardState}. */
public static final class Builder {
-
- private final int mDisplayId;
-
private boolean mKeyguardShowing;
private boolean mAodShowing;
- /**
- * @param displayId the display of this {@link KeyguardState}.
- */
- public Builder(int displayId) {
- mDisplayId = displayId;
+ public Builder() {
}
/**
@@ -154,7 +133,7 @@ public final class KeyguardState implements Parcelable {
*/
@NonNull
public KeyguardState build() {
- return new KeyguardState(mDisplayId, mKeyguardShowing, mAodShowing);
+ return new KeyguardState(mKeyguardShowing, mAodShowing);
}
}
}
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index cc2329fc47cb..f8899c5764aa 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -163,7 +163,6 @@ public class SystemPerformanceHinter {
// The active sessions
private final ArrayList<HighPerfSession> mActiveSessions = new ArrayList<>();
private final SurfaceControl.Transaction mTransaction;
- private final PerformanceHintManager mPerfHintManager;
private @Nullable PerformanceHintManager.Session mAdpfSession;
private @Nullable DisplayRootProvider mDisplayRootProvider;
@@ -184,7 +183,6 @@ public class SystemPerformanceHinter {
@Nullable DisplayRootProvider displayRootProvider,
@Nullable Supplier<SurfaceControl.Transaction> transactionSupplier) {
mDisplayRootProvider = displayRootProvider;
- mPerfHintManager = context.getSystemService(PerformanceHintManager.class);
mTransaction = transactionSupplier != null
? transactionSupplier.get()
: new SurfaceControl.Transaction();
@@ -273,7 +271,7 @@ public class SystemPerformanceHinter {
asyncTraceBegin(HINT_SF_EARLY_WAKEUP, Display.INVALID_DISPLAY);
}
}
- if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
+ if (mAdpfSession != null && nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
if (isTraceEnabled) {
asyncTraceBegin(HINT_ADPF, Display.INVALID_DISPLAY);
@@ -323,7 +321,7 @@ public class SystemPerformanceHinter {
asyncTraceEnd(HINT_SF_EARLY_WAKEUP);
}
}
- if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
+ if (mAdpfSession != null && nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
if (isTraceEnabled) {
asyncTraceEnd(HINT_ADPF);
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index fe936f77de07..f42c0ec5ee7c 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -44,10 +44,10 @@ public final class TransitionRequestInfo implements Parcelable {
private @Nullable ActivityManager.RunningTaskInfo mTriggerTask;
/**
- * If non-null, the task containing the pip activity that participates in this
- * transition.
+ * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+ * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
*/
- private @Nullable ActivityManager.RunningTaskInfo mPipTask;
+ private @Nullable TransitionRequestInfo.PipChange mPipChange;
/** If non-null, a remote-transition associated with the source of this transition. */
private @Nullable RemoteTransition mRemoteTransition;
@@ -70,7 +70,7 @@ public final class TransitionRequestInfo implements Parcelable {
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition) {
- this(type, triggerTask, null /* pipTask */,
+ this(type, triggerTask, null /* pipChange */,
remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);
}
@@ -80,7 +80,7 @@ public final class TransitionRequestInfo implements Parcelable {
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition,
int flags) {
- this(type, triggerTask, null /* pipTask */,
+ this(type, triggerTask, null /* pipChange */,
remoteTransition, null /* displayChange */, flags, -1 /* debugId */);
}
@@ -91,7 +91,7 @@ public final class TransitionRequestInfo implements Parcelable {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
int flags) {
- this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags,
+ this(type, triggerTask, null /* pipChange */, remoteTransition, displayChange, flags,
-1 /* debugId */);
}
@@ -103,7 +103,9 @@ public final class TransitionRequestInfo implements Parcelable {
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
int flags) {
- this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);
+ this(type, triggerTask,
+ pipTask != null ? new TransitionRequestInfo.PipChange(pipTask) : null,
+ remoteTransition, displayChange, flags, -1 /* debugId */);
}
/** @hide */
@@ -252,7 +254,7 @@ public final class TransitionRequestInfo implements Parcelable {
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected DisplayChange(@android.annotation.NonNull android.os.Parcel in) {
+ /* package-private */ DisplayChange(@android.annotation.NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -289,7 +291,7 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1697564781403L,
+ time = 1733334462577L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -302,6 +304,143 @@ public final class TransitionRequestInfo implements Parcelable {
}
+ @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
+ public static final class PipChange implements Parcelable {
+ // In AE case, we might care about the TF token instead of the task token.
+ @android.annotation.NonNull
+ private WindowContainerToken mTaskFragmentToken;
+
+ @android.annotation.NonNull
+ private ActivityManager.RunningTaskInfo mTaskInfo;
+
+ /** Create empty display-change. */
+ public PipChange(ActivityManager.RunningTaskInfo taskInfo) {
+ mTaskFragmentToken = taskInfo.token;
+ mTaskInfo = taskInfo;
+ }
+
+ /** Create a display-change representing a rotation. */
+ public PipChange(WindowContainerToken taskFragmentToken,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ mTaskFragmentToken = taskFragmentToken;
+ mTaskInfo = taskInfo;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/TransitionRequestInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull WindowContainerToken getTaskFragmentToken() {
+ return mTaskFragmentToken;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull ActivityManager.RunningTaskInfo getTaskInfo() {
+ return mTaskInfo;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull PipChange setTaskFragmentToken(@android.annotation.NonNull WindowContainerToken value) {
+ mTaskFragmentToken = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ android.annotation.NonNull.class, null, mTaskFragmentToken);
+ return this;
+ }
+
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull PipChange setTaskInfo(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
+ mTaskInfo = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ android.annotation.NonNull.class, null, mTaskInfo);
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "PipChange { " +
+ "taskFragmentToken = " + mTaskFragmentToken + ", " +
+ "taskInfo = " + mTaskInfo +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mTaskFragmentToken, flags);
+ dest.writeTypedObject(mTaskInfo, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ PipChange(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ WindowContainerToken taskFragmentToken = (WindowContainerToken) in.readTypedObject(WindowContainerToken.CREATOR);
+ ActivityManager.RunningTaskInfo taskInfo = (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+
+ this.mTaskFragmentToken = taskFragmentToken;
+ com.android.internal.util.AnnotationValidations.validate(
+ android.annotation.NonNull.class, null, mTaskFragmentToken);
+ this.mTaskInfo = taskInfo;
+ com.android.internal.util.AnnotationValidations.validate(
+ android.annotation.NonNull.class, null, mTaskInfo);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<PipChange> CREATOR
+ = new Parcelable.Creator<PipChange>() {
+ @Override
+ public PipChange[] newArray(int size) {
+ return new PipChange[size];
+ }
+
+ @Override
+ public PipChange createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new PipChange(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1733334462588L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
+ inputSignatures = "private @android.annotation.NonNull android.window.WindowContainerToken mTaskFragmentToken\nprivate @android.annotation.NonNull android.app.ActivityManager.RunningTaskInfo mTaskInfo\nclass PipChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
@@ -326,9 +465,9 @@ public final class TransitionRequestInfo implements Parcelable {
* @param triggerTask
* If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
- * @param pipTask
- * If non-null, the task containing the pip activity that participates in this
- * transition.
+ * @param pipChange
+ * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+ * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
* @param remoteTransition
* If non-null, a remote-transition associated with the source of this transition.
* @param displayChange
@@ -344,7 +483,7 @@ public final class TransitionRequestInfo implements Parcelable {
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
- @Nullable ActivityManager.RunningTaskInfo pipTask,
+ @Nullable TransitionRequestInfo.PipChange pipChange,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
int flags,
@@ -353,7 +492,7 @@ public final class TransitionRequestInfo implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
this.mTriggerTask = triggerTask;
- this.mPipTask = pipTask;
+ this.mPipChange = pipChange;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
@@ -380,12 +519,12 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
- * If non-null, the task containing the pip activity that participates in this
- * transition.
+ * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+ * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
*/
@DataClass.Generated.Member
- public @Nullable ActivityManager.RunningTaskInfo getPipTask() {
- return mPipTask;
+ public @Nullable TransitionRequestInfo.PipChange getPipChange() {
+ return mPipChange;
}
/**
@@ -433,12 +572,12 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
- * If non-null, the task containing the pip activity that participates in this
- * transition.
+ * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both
+ * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity.
*/
@DataClass.Generated.Member
- public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
- mPipTask = value;
+ public @android.annotation.NonNull TransitionRequestInfo setPipChange(@android.annotation.NonNull TransitionRequestInfo.PipChange value) {
+ mPipChange = value;
return this;
}
@@ -471,10 +610,10 @@ public final class TransitionRequestInfo implements Parcelable {
return "TransitionRequestInfo { " +
"type = " + typeToString() + ", " +
"triggerTask = " + mTriggerTask + ", " +
- "pipTask = " + mPipTask + ", " +
+ "pipChange = " + mPipChange + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
"displayChange = " + mDisplayChange + ", " +
- "flags = " + Integer.toHexString(mFlags) + ", " +
+ "flags = " + mFlags + ", " +
"debugId = " + mDebugId +
" }";
}
@@ -487,13 +626,13 @@ public final class TransitionRequestInfo implements Parcelable {
byte flg = 0;
if (mTriggerTask != null) flg |= 0x2;
- if (mPipTask != null) flg |= 0x4;
+ if (mPipChange != null) flg |= 0x4;
if (mRemoteTransition != null) flg |= 0x8;
if (mDisplayChange != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeInt(mType);
if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
- if (mPipTask != null) dest.writeTypedObject(mPipTask, flags);
+ if (mPipChange != null) dest.writeTypedObject(mPipChange, flags);
if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
dest.writeInt(mFlags);
@@ -514,7 +653,7 @@ public final class TransitionRequestInfo implements Parcelable {
byte flg = in.readByte();
int type = in.readInt();
ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
- ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+ TransitionRequestInfo.PipChange pipChange = (flg & 0x4) == 0 ? null : (TransitionRequestInfo.PipChange) in.readTypedObject(TransitionRequestInfo.PipChange.CREATOR);
RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
int flags = in.readInt();
@@ -524,7 +663,7 @@ public final class TransitionRequestInfo implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
this.mTriggerTask = triggerTask;
- this.mPipTask = pipTask;
+ this.mPipChange = pipChange;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
@@ -548,10 +687,10 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1697564781438L,
+ time = 1733334462604L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
- inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.PipChange mPipChange\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6e76d8d345b2..a551fe701c5b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -166,7 +166,7 @@ public class WindowTokenClient extends Binder {
@VisibleForTesting
public void onConfigurationChangedInner(@NonNull Context context,
@NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
+ CompatibilityInfo.applyOverrideIfNeeded(newConfig);
final boolean displayChanged;
final boolean shouldUpdateResources;
final int diff;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 7ad14b0c9fe8..801698caff0e 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -112,6 +112,18 @@ flag {
}
flag {
+ name: "app_compat_async_relayout"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether we use the SurfaceViewHost overload to apply a change in /n"
+ "position and size in a Transaction provided by a callback invoked /n"
+ "after the View relayout."
+ bug: "322463856"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 8019e6791cf1..a04071a5997b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -58,10 +58,13 @@ flag {
}
flag {
- name: "enable_desktop_windowing_scvh_cache"
+ name: "enable_desktop_windowing_scvh_cache_bug_fix"
namespace: "lse_desktop_experience"
description: "Enables a SurfaceControlViewHost cache for window decorations"
- bug: "345146928"
+ bug: "360452034"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -242,13 +245,6 @@ flag {
}
flag {
- name: "enable_desktop_windowing_app_handle_education_integration"
- namespace: "lse_desktop_experience"
- description: "Enables desktop windowing app handle education and integrates new APIs"
- bug: "380272815"
-}
-
-flag {
name: "enable_desktop_windowing_transitions"
namespace: "lse_desktop_experience"
description: "Enables desktop windowing transition & motion polish changes"
@@ -301,6 +297,14 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables desktop windowing app-to-web education"
bug: "348205896"
+ is_exported: true
+}
+
+flag {
+ name: "enable_desktop_windowing_app_to_web_education_integration"
+ namespace: "lse_desktop_experience"
+ description: "Enables desktop windowing App-to-Web education and integrates new APIs"
+ bug: "380272815"
}
flag {
@@ -445,6 +449,14 @@ flag {
}
flag {
+ name: "reparent_window_token_api"
+ namespace: "lse_desktop_experience"
+ description: "Allows to reparent a window token to a different display"
+ is_fixed_read_only: true
+ bug: "381258683"
+}
+
+flag {
name: "enable_desktop_windowing_hsum"
namespace: "lse_desktop_experience"
description: "Enables HSUM on desktop mode."
@@ -456,4 +468,25 @@ flag {
namespace: "lse_desktop_experience"
description: "Enable multiple desktop sessions for desktop windowing."
bug: "379158791"
+}
+
+flag {
+ name: "enable_connected_displays_dnd"
+ namespace: "lse_desktop_experience"
+ description: "Enable drag-and-drop between connected displays."
+ bug: "381793841"
+}
+
+flag {
+ name: "enable_connected_displays_window_drag"
+ namespace: "lse_desktop_experience"
+ description: "Enable window drag between connected displays."
+ bug: "381172172"
+}
+
+flag {
+ name: "enable_bug_fixes_for_secondary_display"
+ namespace: "lse_desktop_experience"
+ description: "Bugfixes / papercuts to bring Desktop Windowing to secondary displays."
+ bug: "382023296"
} \ No newline at end of file
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index b2f125dd2821..d5ba32cafebd 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -48,6 +48,7 @@ flag {
namespace: "responsible_apis"
description: "Introduce additional start modes."
bug: "352182359"
+ is_exported: true
}
flag {
@@ -55,6 +56,7 @@ flag {
namespace: "responsible_apis"
description: "Add options parameter to IntentSender.sendIntent."
bug: "339720406"
+ is_exported: true
}
flag {
@@ -63,6 +65,7 @@ flag {
description: "Strict mode flag"
bug: "324089586"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index efacc346ac0a..1ddfe878dfd8 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -14,6 +14,7 @@ flag {
namespace: "systemui"
description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
bug: "281648899"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ebbe4830009c..30f0c7371270 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,6 +368,7 @@ flag {
description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -376,6 +377,7 @@ flag {
description: "expose timestamp in BackEvent (API extension)"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -384,6 +386,7 @@ flag {
description: "EDGE_NONE swipeEdge option in BackEvent"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -422,6 +425,7 @@ flag {
description: "Provide pre-make predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -449,3 +453,11 @@ flag {
is_fixed_read_only: true
bug: "376407910"
}
+
+flag {
+ name: "relative_insets"
+ namespace: "windowing_frontend"
+ description: "Support insets definition and calculation relative to task bounds."
+ bug: "277292497"
+ is_fixed_read_only: true
+} \ 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 b7012b68d459..d0d4af6ea598 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -100,6 +100,7 @@ flag {
name: "touch_pass_through_opt_in"
description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
bug: "358129114"
+ is_exported: true
}
flag {
@@ -115,3 +116,14 @@ flag {
description: "Relax the assumption of non-match parent activity"
bug: "356277166"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "allow_multiple_adjacent_task_fragments"
+ description: "Refactor to allow more than 2 adjacent TaskFragments"
+ bug: "373709676"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index e8831ec2743e..a27eeb8fdd63 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -58,7 +58,6 @@ import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.widget.Toast;
import com.android.internal.R;
@@ -289,9 +288,7 @@ public class AccessibilityShortcutController {
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.SHOWN,
userId);
} else {
- if (Flags.restoreA11yShortcutTargetService()) {
- enableDefaultHardwareShortcut(userId);
- }
+ enableDefaultHardwareShortcut(userId);
playNotificationTone();
if (mAlertDialog != null) {
mAlertDialog.dismiss();
@@ -360,43 +357,44 @@ public class AccessibilityShortcutController {
// Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
// Put "don't turn on" as the primary action.
- final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
- // Use SystemUI context so we pick up any theme set in a vendor overlay
- mFrameworkObjectProvider.getSystemUiContext())
- .setTitle(getShortcutWarningTitle(targets))
- .setMessage(getShortcutWarningMessage(targets))
- .setCancelable(false)
- .setNegativeButton(R.string.accessibility_shortcut_on,
- (DialogInterface d, int which) -> enableDefaultHardwareShortcut(userId))
- .setPositiveButton(R.string.accessibility_shortcut_off,
- (DialogInterface d, int which) -> {
- Set<String> targetServices =
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext,
- HARDWARE,
+ final AlertDialog alertDialog =
+ mFrameworkObjectProvider
+ .getAlertDialogBuilder(
+ // Use SystemUI context so we pick up any theme set in a vendor
+ // overlay
+ mFrameworkObjectProvider.getSystemUiContext())
+ .setTitle(getShortcutWarningTitle(targets))
+ .setMessage(getShortcutWarningMessage(targets))
+ .setCancelable(false)
+ .setNegativeButton(
+ R.string.accessibility_shortcut_on,
+ (DialogInterface d, int which) ->
+ enableDefaultHardwareShortcut(userId))
+ .setPositiveButton(
+ R.string.accessibility_shortcut_off,
+ (DialogInterface d, int which) -> {
+ Set<String> targetServices =
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, HARDWARE, userId);
+ am.enableShortcutsForTargets(
+ false, HARDWARE, targetServices, userId);
+ // If canceled, treat as if the dialog has never been shown
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStatus.NOT_SHOWN,
+ userId);
+ })
+ .setOnCancelListener(
+ (DialogInterface d) -> {
+ // If canceled, treat as if the dialog has never been shown
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStatus.NOT_SHOWN,
userId);
- if (Flags.migrateEnableShortcuts()) {
- am.enableShortcutsForTargets(
- false, HARDWARE, targetServices, userId);
- } else {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- mContext, targetServices, userId);
- }
- // If canceled, treat as if the dialog has never been shown
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- DialogStatus.NOT_SHOWN, userId);
- })
- .setOnCancelListener((DialogInterface d) -> {
- // If canceled, treat as if the dialog has never been shown
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- DialogStatus.NOT_SHOWN, userId);
- })
- .create();
+ })
+ .create();
return alertDialog;
}
@@ -531,14 +529,8 @@ public class AccessibilityShortcutController {
// Default service is invalid, so nothing we can do here.
return;
}
- if (Flags.migrateEnableShortcuts()) {
- accessibilityManager.enableShortcutsForTargets(true, HARDWARE,
- Set.of(defaultServiceComponent.flattenToString()), userId);
- } else {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- defaultServiceComponent.flattenToString(), userId);
- }
+ accessibilityManager.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(defaultServiceComponent.flattenToString()), userId);
}
private boolean performTtsPrompt(AlertDialog alertDialog) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
index 3557633f87c5..2e1a0bff3cd3 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
@@ -29,7 +29,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.view.accessibility.Flags;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -65,9 +64,6 @@ public class AccessibilityServiceWarning {
Window window = ad.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
- if (!Flags.warningUseDefaultDialogType()) {
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
- }
window.setAttributes(params);
return ad;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index a753110edd51..f0582d063c71 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -19,8 +19,6 @@ package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,7 +29,6 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -118,18 +115,9 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
@Override
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
- if (Flags.migrateEnableShortcuts()) {
- final AccessibilityManager am =
- getContext().getSystemService(AccessibilityManager.class);
- am.enableShortcutsForTargets(
- isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
- } else {
- if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
- } else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
- }
- }
+ final AccessibilityManager am = getContext().getSystemService(AccessibilityManager.class);
+ am.enableShortcutsForTargets(
+ isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
}
public void setStateDescription(CharSequence stateDescription) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index f690bd3477f0..5d4c40853009 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -20,9 +20,13 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.Nullable;
import android.app.AlertDialog;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
@@ -58,6 +62,7 @@ import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
+import android.widget.flags.Flags;
import com.android.internal.R;
@@ -66,6 +71,12 @@ import java.lang.ref.WeakReference;
public class AlertController {
public static final int MICRO = 1;
+ private static boolean sUseWearMaterial3Style;
+
+ @ChangeId
+ @EnabledSince(targetSdkVersion = 36)
+ private static final long WEAR_MATERIAL3_ALERTDIALOG = 379365266L;
+
private final Context mContext;
private final DialogInterface mDialogInterface;
protected final Window mWindow;
@@ -210,7 +221,8 @@ public class AlertController {
mHandler = new ButtonHandler(di);
final TypedArray a = context.obtainStyledAttributes(null,
- R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+ R.styleable.AlertDialog, getAlertDialogDefStyleAttr(context),
+ getAlertDialogDefStyleRes());
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
@@ -236,6 +248,30 @@ public class AlertController {
window.requestFeature(Window.FEATURE_NO_TITLE);
}
+ private int getAlertDialogDefStyleAttr(Context context) {
+ sUseWearMaterial3Style = useWearMaterial3Style(context);
+ if (sUseWearMaterial3Style) {
+ return 0;
+ }
+ return R.attr.alertDialogStyle;
+ }
+
+ private int getAlertDialogDefStyleRes() {
+ if (sUseWearMaterial3Style) {
+ return com.android.internal.R.style.AlertDialog_DeviceDefault_WearMaterial3;
+ }
+ return 0;
+ }
+
+ private static boolean useWearMaterial3Style(Context context) {
+ return Flags.useWearMaterial3Ui()
+ && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_ALERTDIALOG)
+ && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && (context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault
+ || context.getThemeResId()
+ == com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert);
+ }
+
static boolean canTextInput(View v) {
if (v.onCheckIsTextEditor()) {
return true;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 2cfc680a3fe8..f01aa80fab4f 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -163,4 +163,5 @@ interface IAppOpsService {
void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName,
@nullable String attributionTag, int virtualDeviceId);
List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId);
+ oneway void noteOperationsInBatch(in Map batchedNoteOps);
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index ee5bd65e76de..644d69919998 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -70,6 +70,7 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -352,6 +353,7 @@ public class IntentForwarderActivity extends Activity {
findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish());
findViewById(R.id.button_open).setOnClickListener(v -> {
+ TargetInfo.refreshIntentCreatorToken(launchIntent);
startActivityAsCaller(
launchIntent,
ActivityOptions.makeCustomAnimation(
@@ -476,6 +478,7 @@ public class IntentForwarderActivity extends Activity {
private void startActivityAsCaller(Intent newIntent, int userId) {
try {
+ TargetInfo.refreshIntentCreatorToken(newIntent);
startActivityAsCaller(
newIntent,
/* options= */ null,
@@ -502,6 +505,7 @@ public class IntentForwarderActivity extends Activity {
return;
}
sanitizeIntent(innerIntent);
+ TargetInfo.refreshIntentCreatorToken(intentReceived);
startActivityAsCaller(intentReceived, null, false, getUserId());
finish();
}
@@ -525,6 +529,7 @@ public class IntentForwarderActivity extends Activity {
if (singleTabOnly) {
intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
}
+ TargetInfo.refreshIntentCreatorToken(intentReceived);
startActivityAsCaller(intentReceived, null, false, userId);
finish();
}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 473134ea46f3..0c650774105e 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -173,6 +173,7 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable {
@Override
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
+ TargetInfo.refreshIntentCreatorToken(mResolvedIntent);
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
}
@@ -180,6 +181,7 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable {
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
+ TargetInfo.refreshIntentCreatorToken(mResolvedIntent);
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index d7f3a76c61e0..0eaa43d2c6e8 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -260,6 +260,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
intent.setComponent(mChooserTarget.getComponentName());
intent.putExtras(mChooserTarget.getIntentExtras());
TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId);
+ TargetInfo.refreshIntentCreatorToken(intent);
// Important: we will ignore the target security checks in ActivityManager
// if and only if the ChooserTarget's target package is the same package
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
index 7bb7ddc65c6d..fcf5883cc84b 100644
--- a/core/java/com/android/internal/app/chooser/TargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -17,13 +17,17 @@
package com.android.internal.app.chooser;
+import static android.security.Flags.preventIntentRedirect;
+
import android.app.Activity;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import com.android.internal.app.ResolverActivity;
@@ -141,4 +145,20 @@ public interface TargetInfo {
intent.fixUris(currentUserId);
}
}
+
+ /**
+ * refreshes intent's creatorToken with its current intent key fields. This allows
+ * ChooserActivity to still keep original creatorToken's creator uid after making changes to
+ * the intent and still keep it valid.
+ * @param intent the intent's creatorToken needs to up refreshed.
+ */
+ static void refreshIntentCreatorToken(Intent intent) {
+ if (!preventIntentRedirect()) return;
+ try {
+ intent.setCreatorToken(ActivityManager.getService().refreshIntentCreatorToken(
+ intent.cloneForCreatorToken()));
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failure from system", e);
+ }
+ }
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index eb6a81031321..592ea9e5e600 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -89,6 +89,9 @@ import java.lang.annotation.Retention;
SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+ SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED,
+ SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
+ SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS,
})
public @interface SoftInputShowHideReason {
/** Default, undefined reason. */
@@ -337,6 +340,18 @@ public @interface SoftInputShowHideReason {
int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT;
/**
+ * Show soft input because the input target changed
+ * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+ */
+ int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED;
+
+ /**
+ * Hide soft input because the input target changed by
+ * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+ */
+ int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED;
+
+ /**
* Show / Hide soft input by
* {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}.
*/
@@ -404,4 +419,7 @@ public @interface SoftInputShowHideReason {
* {@link android.view.InsetsController#controlWindowInsetsAnimation}.
*/
int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
+
+ /** Hide soft input when the window lost focus. */
+ int REASON_HIDE_WINDOW_LOST_FOCUS = ImeProtoEnums.REASON_HIDE_WINDOW_LOST_FOCUS;
}
diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
index 66ee131badac..dfec49907c69 100644
--- a/core/java/com/android/internal/jank/EventLogTags.logtags
+++ b/core/java/com/android/internal/jank/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.internal.jank;
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 2834e6883316..454323b60333 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -685,14 +685,6 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
}
}
- ThreadedRendererWrapper getThreadedRenderer() {
- return mRendererWrapper;
- }
-
- ViewRootWrapper getViewRoot() {
- return mViewRoot;
- }
-
private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
&& missedFramesCount >= mTraceThresholdMissedFrames;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index ef08e49ce6d9..26ff43009ef7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -31,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.ActivityThread;
+import android.app.Application;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
@@ -184,10 +185,12 @@ public class InteractionJankMonitor {
@GuardedBy("mLock")
private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
private final Handler mWorker;
+ private final Application mCurrentApplication;
private final DisplayResolutionTracker mDisplayResolutionTracker;
private final Object mLock = new Object();
private @ColorInt int mDebugBgColor = Color.CYAN;
private double mDebugYOffset = 0.1;
+ @GuardedBy("mLock")
private InteractionMonitorDebugOverlay mDebugOverlay;
private volatile boolean mEnabled = DEFAULT_ENABLED;
@@ -216,13 +219,15 @@ public class InteractionJankMonitor {
mWorker = worker.getThreadHandler();
mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
- final Context context = ActivityThread.currentApplication();
- if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+ mCurrentApplication = ActivityThread.currentApplication();
+ if (mCurrentApplication == null || mCurrentApplication.checkCallingOrSelfPermission(
+ READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
+ " enabled=" + mEnabled + ", interval=" + mSamplingInterval
+ ", missedFrameThreshold=" + mTraceThresholdMissedFrames
+ ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
- + ", package=" + (context == null ? "null" : context.getPackageName()));
+ + ", package=" + (mCurrentApplication == null ? "null"
+ : mCurrentApplication.getPackageName()));
return;
}
@@ -234,8 +239,8 @@ public class InteractionJankMonitor {
new HandlerExecutor(mWorker), this::updateProperties);
} catch (SecurityException ex) {
Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
- + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
- + ", package=" + context.getPackageName());
+ + mCurrentApplication.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ + ", package=" + mCurrentApplication.getPackageName());
}
});
}
@@ -405,7 +410,11 @@ public class InteractionJankMonitor {
RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
new RunningTracker(
- conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT)));
+ conf, createFrameTracker(conf), () -> {
+ Log.w(TAG, "CUJ cancelled due to timeout, CUJ="
+ + Cuj.getNameOfCuj(cujType));
+ cancel(cujType, REASON_CANCEL_TIMEOUT);
+ }));
if (tracker == null) {
return false;
}
@@ -534,7 +543,7 @@ public class InteractionJankMonitor {
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerAdded(cuj, tracker);
+ mDebugOverlay.onTrackerAdded(cuj, tracker.mTracker.hashCode());
}
return tracker;
@@ -569,7 +578,7 @@ public class InteractionJankMonitor {
running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
mRunningTrackers.remove(cuj);
if (mDebugOverlay != null) {
- mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
+ mDebugOverlay.onTrackerRemoved(cuj, reason, tracker.hashCode());
}
return false;
}
@@ -592,14 +601,18 @@ public class InteractionJankMonitor {
mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
// Never allow the debug overlay to be used on user builds
- boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
- && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
- if (debugOverlayEnabled && mDebugOverlay == null) {
- mDebugOverlay = new InteractionMonitorDebugOverlay(
- mLock, mDebugBgColor, mDebugYOffset);
- } else if (!debugOverlayEnabled && mDebugOverlay != null) {
- mDebugOverlay.dispose();
- mDebugOverlay = null;
+ if (Build.IS_USER) break;
+ boolean debugOverlayEnabled = properties.getBoolean(property,
+ DEFAULT_DEBUG_OVERLAY_ENABLED);
+ synchronized (mLock) {
+ if (debugOverlayEnabled && mDebugOverlay == null) {
+ // Use the worker thread as the UI thread for the debug overlay:
+ mDebugOverlay = new InteractionMonitorDebugOverlay(
+ mCurrentApplication, mWorker, mDebugBgColor, mDebugYOffset);
+ } else if (!debugOverlayEnabled && mDebugOverlay != null) {
+ mDebugOverlay.dispose();
+ mDebugOverlay = null;
+ }
}
}
default -> Log.w(TAG, "Got a change event for an unknown property: "
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index d9cac12c3372..97f8879d1ff5 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -16,26 +16,39 @@
package com.android.internal.jank;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Gravity.CENTER;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
+import android.annotation.AnyThread;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.annotation.UiThread;
-import android.app.ActivityThread;
+import android.app.Application;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.RecordingCanvas;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.WindowCallbacks;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.jank.FrameTracker.Reasons;
+import java.util.ArrayList;
+
/**
* An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
* associated with one of the CUJs being tracked. There's no guarantee which window it will
@@ -50,236 +63,261 @@ import com.android.internal.jank.FrameTracker.Reasons;
* <li> Grey text indicates the CUJ ended normally and is no longer running
* <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
* </ul>
+ *
* @hide
*/
-class InteractionMonitorDebugOverlay implements WindowCallbacks {
+class InteractionMonitorDebugOverlay {
private static final String TAG = "InteractionMonitorDebug";
private static final int REASON_STILL_RUNNING = -1000;
- private final Object mLock;
+ private static final long HIDE_OVERLAY_DELAY = 2000L;
// Sparse array where the key in the CUJ and the value is the session status, or null if
// it's currently running
- @GuardedBy("mLock")
- private final SparseIntArray mRunningCujs = new SparseIntArray();
- private Handler mHandler = null;
- private FrameTracker.ViewRootWrapper mViewRoot = null;
- private final Paint mDebugPaint;
- private final Paint.FontMetrics mDebugFontMetrics;
- // Used to display the overlay in a different color and position for different processes.
- // Otherwise, two overlays will overlap and be difficult to read.
- private final int mBgColor;
- private final double mYOffset;
- private final String mPackageName;
- private static final String TRACK_NAME = "InteractionJankMonitor";
-
- InteractionMonitorDebugOverlay(Object lock, @ColorInt int bgColor, double yOffset) {
- mLock = lock;
- mBgColor = bgColor;
- mYOffset = yOffset;
- mDebugPaint = new Paint();
- mDebugPaint.setAntiAlias(false);
- mDebugFontMetrics = new Paint.FontMetrics();
- final Context context = ActivityThread.currentApplication();
- mPackageName = context == null ? "null" : context.getPackageName();
- }
+ private final Application mCurrentApplication;
+ private final Handler mUiThread;
+ private final DebugOverlayView mDebugOverlayView;
+ private final WindowManager mWindowManager;
+ private final ArrayList<TrackerState> mRunningCujs = new ArrayList<>();
- @UiThread
- void dispose() {
- if (mViewRoot != null && mHandler != null) {
- mHandler.runWithScissors(() -> mViewRoot.removeWindowCallbacks(this),
- InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
- forceRedraw();
+ InteractionMonitorDebugOverlay(@NonNull Application currentApplication,
+ @NonNull @UiThread Handler uiThread, @ColorInt int bgColor, double yOffset) {
+ mCurrentApplication = currentApplication;
+ mUiThread = uiThread;
+ final Display display = mCurrentApplication.getSystemService(
+ DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ final Context windowContext = mCurrentApplication.createDisplayContext(
+ display).createWindowContext(TYPE_SYSTEM_OVERLAY, null /* options */);
+ mWindowManager = windowContext.getSystemService(WindowManager.class);
+
+ final Rect size = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitInsetsTypes(0 /* types */);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+
+ lp.width = size.width();
+ lp.height = size.height();
+ lp.gravity = CENTER;
+ lp.setTitle("InteractionMonitorDebugOverlay");
+
+ if (!mUiThread.getLooper().isCurrentThread()) {
+ Log.e(TAG, "InteractionMonitorDebugOverlay must be constructed on "
+ + "InteractionJankMonitor's worker thread");
}
- mHandler = null;
- mViewRoot = null;
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
+ mDebugOverlayView = new DebugOverlayView(mCurrentApplication, bgColor, yOffset);
+ mWindowManager.addView(mDebugOverlayView, lp);
}
- @UiThread
- private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
- FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
- if (mViewRoot == null && viewRoot != null) {
- // Add a trace marker so we can identify traces that were captured while the debug
- // overlay was enabled. Traces that use the debug overlay should NOT be used for
- // performance analysis.
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- mHandler = tracker.mConfig.getHandler();
- mViewRoot = viewRoot;
- mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
- InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
- forceRedraw();
- return true;
+ private final Runnable mHideOverlayRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mRunningCujs.clear();
+ mDebugOverlayView.setVisibility(INVISIBLE);
}
- return false;
+ };
+
+ @AnyThread
+ void onTrackerAdded(@Cuj.CujType int addedCuj, int cookie) {
+ mUiThread.removeCallbacks(mHideOverlayRunnable);
+ mUiThread.post(() -> {
+ String cujName = Cuj.getNameOfCuj(addedCuj);
+ Log.i(TAG, cujName + " started (cookie=" + cookie + ")");
+ mRunningCujs.add(new TrackerState(addedCuj, cookie));
+ mDebugOverlayView.setVisibility(VISIBLE);
+ mDebugOverlayView.invalidate();
+ });
}
- @GuardedBy("mLock")
- private float getWidthOfLongestCujName(int cujFontSize) {
- mDebugPaint.setTextSize(cujFontSize);
- float maxLength = 0;
- for (int i = 0; i < mRunningCujs.size(); i++) {
- String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
- float textLength = mDebugPaint.measureText(cujName);
- if (textLength > maxLength) {
- maxLength = textLength;
+ @AnyThread
+ void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason, int cookie) {
+ mUiThread.post(() -> {
+ TrackerState foundTracker = null;
+ boolean allTrackersEnded = true;
+ for (int i = 0; i < mRunningCujs.size(); i++) {
+ TrackerState tracker = mRunningCujs.get(i);
+ if (tracker.mCuj == removedCuj && tracker.mCookie == cookie) {
+ foundTracker = tracker;
+ } else {
+ // If none of the trackers have REASON_STILL_RUNNING status, then
+ // all CUJs have ended
+ allTrackersEnded = allTrackersEnded && tracker.mState != REASON_STILL_RUNNING;
+ }
}
- }
- return maxLength;
- }
- private float getTextHeight(int textSize) {
- mDebugPaint.setTextSize(textSize);
- mDebugPaint.getFontMetrics(mDebugFontMetrics);
- return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
+ if (foundTracker != null) {
+ foundTracker.mState = reason;
+ }
+
+ String cujName = Cuj.getNameOfCuj(removedCuj);
+ Log.i(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled")
+ + " (cookie=" + cookie + ")");
+
+ if (allTrackersEnded) {
+ Log.i(TAG, "All CUJs ended");
+ mUiThread.postDelayed(mHideOverlayRunnable, HIDE_OVERLAY_DELAY);
+ }
+ mDebugOverlayView.invalidate();
+ });
}
- private int dipToPx(int dip) {
- if (mViewRoot != null) {
- return mViewRoot.dipToPx(dip);
- } else {
- return dip;
- }
+ @AnyThread
+ void dispose() {
+ mUiThread.post(() -> {
+ mWindowManager.removeView(mDebugOverlayView);
+ });
}
- @UiThread
- private void forceRedraw() {
- if (mViewRoot != null && mHandler != null) {
- mHandler.runWithScissors(() -> {
- mViewRoot.requestInvalidateRootRenderNode();
- mViewRoot.getView().invalidate();
- }, InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
+ @AnyThread
+ private static class TrackerState {
+ final int mCookie;
+ final int mCuj;
+ int mState;
+
+ private TrackerState(int cuj, int cookie) {
+ mCuj = cuj;
+ mCookie = cookie;
+ // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+ // is still running
+ mState = REASON_STILL_RUNNING;
}
}
@UiThread
- void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
- SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
- synchronized (mLock) {
- mRunningCujs.put(removedCuj, reason);
- boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
- if (isLoggable) {
- String cujName = Cuj.getNameOfCuj(removedCuj);
- Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
- }
- // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
- if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
- if (isLoggable) Log.d(TAG, "All CUJs ended");
- mRunningCujs.clear();
- dispose();
- } else {
- boolean needsNewViewRoot = true;
- if (mViewRoot != null) {
- // Check to see if this viewroot is still associated with one of the running
- // trackers
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (mViewRoot.equals(
- runningTrackers.valueAt(i).mTracker.getViewRoot())) {
- needsNewViewRoot = false;
- break;
- }
- }
- }
- if (needsNewViewRoot) {
- dispose();
- for (int i = 0; i < runningTrackers.size(); i++) {
- if (attachViewRootIfNeeded(runningTrackers.valueAt(i))) {
- break;
- }
- }
- } else {
- forceRedraw();
- }
- }
+ private class DebugOverlayView extends View {
+ private static final String TRACK_NAME = "InteractionJankMonitor";
+
+ // Used to display the overlay in a different color and position for different processes.
+ // Otherwise, two overlays will overlap and be difficult to read.
+ private final int mBgColor;
+ private final double mYOffset;
+
+ private final float mDensity;
+ private final Paint mDebugPaint;
+ private final Paint.FontMetrics mDebugFontMetrics;
+ private final String mPackageNameText;
+
+ final int mPadding;
+ final int mPackageNameFontSize;
+ final int mCujFontSize;
+ final float mCujNameTextHeight;
+ final float mCujStatusWidth;
+ final float mPackageNameTextHeight;
+ final float mPackageNameWidth;
+
+ private DebugOverlayView(Context context, @ColorInt int bgColor, double yOffset) {
+ super(context);
+ setVisibility(INVISIBLE);
+ mBgColor = bgColor;
+ mYOffset = yOffset;
+ final DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
+ mDensity = displayMetrics.density;
+ mDebugPaint = new Paint();
+ mDebugPaint.setAntiAlias(false);
+ mDebugFontMetrics = new Paint.FontMetrics();
+ mPackageNameText = "package:" + mCurrentApplication.getPackageName();
+ mPadding = dipToPx(5);
+ mPackageNameFontSize = dipToPx(12);
+ mCujFontSize = dipToPx(18);
+ mCujNameTextHeight = getTextHeight(mCujFontSize);
+ mCujStatusWidth = mCujNameTextHeight * 1.2f;
+ mPackageNameTextHeight = getTextHeight(mPackageNameFontSize);
+ mPackageNameWidth = getWidthOfText(mPackageNameText, mPackageNameFontSize);
}
- }
- @UiThread
- void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- String cujName = Cuj.getNameOfCuj(addedCuj);
- Log.d(TAG, cujName + " started");
+ private int dipToPx(int dip) {
+ return (int) (mDensity * dip + 0.5f);
}
- synchronized (mLock) {
- // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
- // is still running
- mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
- attachViewRootIfNeeded(tracker);
- forceRedraw();
+
+ private float getTextHeight(int textSize) {
+ mDebugPaint.setTextSize(textSize);
+ mDebugPaint.getFontMetrics(mDebugFontMetrics);
+ return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
}
- }
- @Override
- public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
- Rect systemInsets, Rect stableInsets) {
- }
+ private float getWidthOfText(String text, int fontSize) {
+ mDebugPaint.setTextSize(fontSize);
+ return mDebugPaint.measureText(text);
+ }
- @Override
- public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
- Rect systemInsets, Rect stableInsets) {
- }
+ private float getWidthOfLongestCujName(int cujFontSize) {
+ mDebugPaint.setTextSize(cujFontSize);
+ float maxLength = 0;
+ for (int i = 0; i < mRunningCujs.size(); i++) {
+ String cujName = Cuj.getNameOfCuj(mRunningCujs.get(i).mCuj);
+ float textLength = mDebugPaint.measureText(cujName);
+ if (textLength > maxLength) {
+ maxLength = textLength;
+ }
+ }
+ return maxLength;
+ }
- @Override
- public void onWindowDragResizeEnd() {
- }
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
- @Override
- public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
- return false;
- }
+ // Add a trace marker so we can identify traces that were captured while the debug
+ // overlay was enabled. Traces that use the debug overlay should NOT be used for
+ // performance analysis.
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
- @Override
- public void onRequestDraw(boolean reportNextDraw) {
- }
+ final int h = getHeight();
+ final int w = getWidth();
+ final int dy = (int) (h * mYOffset);
- @Override
- public void onPostDraw(RecordingCanvas canvas) {
- final int padding = dipToPx(5);
- final int h = canvas.getHeight();
- final int w = canvas.getWidth();
- // Draw sysui CUjs near the bottom of the screen so they don't overlap with the shade,
- // and draw launcher CUJs near the top of the screen so they don't overlap with gestures
- final int dy = (int) (h * mYOffset);
- int packageNameFontSize = dipToPx(12);
- int cujFontSize = dipToPx(18);
- final float cujNameTextHeight = getTextHeight(cujFontSize);
- final float packageNameTextHeight = getTextHeight(packageNameFontSize);
-
- synchronized (mLock) {
- float maxLength = getWidthOfLongestCujName(cujFontSize);
+ float maxLength = Math.max(mPackageNameWidth, getWidthOfLongestCujName(mCujFontSize))
+ + mCujStatusWidth;
final int dx = (int) ((w - maxLength) / 2f);
canvas.translate(dx, dy);
// Draw background rectangle for displaying the text showing the CUJ name
mDebugPaint.setColor(mBgColor);
- canvas.drawRect(
- -padding * 2, // more padding on top so we can draw the package name
- -padding,
- padding * 2 + maxLength,
- padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
- mDebugPaint);
- mDebugPaint.setTextSize(packageNameFontSize);
+ canvas.drawRect(-mPadding * 2, // more padding on top so we can draw the package name
+ -mPadding, mPadding * 2 + maxLength, mPadding * 2 + mPackageNameTextHeight
+ + mCujNameTextHeight * mRunningCujs.size(), mDebugPaint);
+ mDebugPaint.setTextSize(mPackageNameFontSize);
mDebugPaint.setColor(Color.BLACK);
mDebugPaint.setStrikeThruText(false);
- canvas.translate(0, packageNameTextHeight);
- canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
- mDebugPaint.setTextSize(cujFontSize);
+ canvas.translate(0, mPackageNameTextHeight);
+ canvas.drawText(mPackageNameText, 0, 0, mDebugPaint);
+ mDebugPaint.setTextSize(mCujFontSize);
// Draw text for CUJ names
for (int i = 0; i < mRunningCujs.size(); i++) {
- int status = mRunningCujs.valueAt(i);
- if (status == REASON_STILL_RUNNING) {
- mDebugPaint.setColor(Color.BLACK);
- mDebugPaint.setStrikeThruText(false);
- } else if (status == REASON_END_NORMAL) {
- mDebugPaint.setColor(Color.GRAY);
- mDebugPaint.setStrikeThruText(false);
- } else {
- // Cancelled, or otherwise ended for a bad reason
- mDebugPaint.setColor(Color.RED);
- mDebugPaint.setStrikeThruText(true);
- }
- String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
- canvas.translate(0, cujNameTextHeight);
- canvas.drawText(cujName, 0, 0, mDebugPaint);
+ TrackerState tracker = mRunningCujs.get(i);
+ int status = tracker.mState;
+ String statusText = switch (status) {
+ case REASON_STILL_RUNNING -> {
+ mDebugPaint.setColor(Color.BLACK);
+ mDebugPaint.setStrikeThruText(false);
+ yield "☐"; // BALLOT BOX
+ }
+ case REASON_END_NORMAL -> {
+ mDebugPaint.setColor(Color.GRAY);
+ mDebugPaint.setStrikeThruText(false);
+ yield "✅"; // WHITE HEAVY CHECK MARK
+ }
+ default -> {
+ // Cancelled, or otherwise ended for a bad reason
+ mDebugPaint.setColor(Color.RED);
+ mDebugPaint.setStrikeThruText(true);
+ yield "❌"; // CROSS MARK
+ }
+ };
+ String cujName = Cuj.getNameOfCuj(tracker.mCuj);
+ canvas.translate(0, mCujNameTextHeight);
+ canvas.drawText(statusText, 0, 0, mDebugPaint);
+ canvas.drawText(cujName, mCujStatusWidth, 0, mDebugPaint);
}
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
}
}
}
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 4400ed117721..1923c5fb9186 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -20,7 +20,7 @@
"file_patterns": [
"BinderDeathDispatcher\\.java"
],
- "name": "FrameworksCoreTests_internal_os_binder"
+ "name": "FrameworksCoreTests_all_binder"
},
{
"file_patterns": [
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fafa08536d6c..cd17ed89058a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -201,6 +201,9 @@ public final class Zygote {
*/
public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
+ /** Load 4KB ELF files on 16KB device using appcompat mode */
+ public static final int ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26;
+
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e402ddfc637a..e60879e02b4b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,6 +19,8 @@ package com.android.internal.os;
import static android.system.OsConstants.S_IRWXG;
import static android.system.OsConstants.S_IRWXO;
+import static android.net.http.Flags.preloadHttpengineInZygote;
+
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START;
@@ -27,6 +29,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.SharedLibraryInfo;
import android.content.res.Resources;
import android.os.Build;
+import android.net.http.HttpEngine;
import android.os.Environment;
import android.os.IInstalld;
import android.os.Process;
@@ -144,6 +147,23 @@ public class ZygoteInit {
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
preloadSharedLibraries();
preloadTextResources();
+
+ // TODO: remove the try/catch and the flag read as soon as the flag is ramped and 25Q2
+ // starts building from source.
+ if (preloadHttpengineInZygote()) {
+ try {
+ HttpEngine.preload();
+ } catch (NoSuchMethodError e){
+ // The flag protecting this API is not an exported
+ // flag because ZygoteInit happens before the
+ // system service has initialized the flag which means
+ // that we can't query the real value of the flag
+ // from the tethering module. In order to avoid crashing
+ // in the case where we have (new zygote, old tethering).
+ // we catch the NoSuchMethodError and just log.
+ Log.d(TAG, "HttpEngine.preload() threw " + e);
+ }
+ }
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index c953d88c9482..445dac7411da 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -25,7 +25,6 @@ import android.aconfig.nano.Aconfig.parsed_flags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Flags;
-import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Process;
import android.util.ArrayMap;
@@ -247,20 +246,23 @@ public class AconfigFlags {
negated = true;
featureFlag = featureFlag.substring(1).strip();
}
- final Boolean flagValue = getFlagValue(featureFlag);
- boolean shouldSkip = false;
+ Boolean flagValue = getFlagValue(featureFlag);
+ boolean isUndefined = false;
if (flagValue == null) {
- Slog.w(LOG_TAG, "Skipping element " + parser.getName()
- + " due to unknown feature flag " + featureFlag);
- shouldSkip = true;
- } else if (flagValue == negated) {
+ isUndefined = true;
+ flagValue = false;
+ }
+ boolean shouldSkip = false;
+ if (flagValue == negated) {
// Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
- Slog.i(LOG_TAG, "Skipping element " + parser.getName()
- + " behind feature flag " + featureFlag + " = " + flagValue);
shouldSkip = true;
}
if (pkg != null && android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
- pkg.addFeatureFlag(featureFlag, flagValue);
+ if (isUndefined) {
+ pkg.addFeatureFlag(featureFlag, null);
+ } else {
+ pkg.addFeatureFlag(featureFlag, flagValue);
+ }
}
return shouldSkip;
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 0f93e6e8109b..c160b42f8b6b 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -29,6 +29,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_
import static android.os.Build.VERSION_CODES.DONUT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.sdk.Flags.majorMinorVersioningScheme;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
@@ -1689,6 +1690,21 @@ public class ParsingPackageUtils {
targetCode = minCode;
}
+ if (majorMinorVersioningScheme()) {
+ val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersionFull);
+ if (val != null) {
+ if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+ String minSdkVersionFullString = val.string.toString();
+ ParseResult<Void> minSdkVersionFullResult =
+ FrameworkParsingPackageUtils.verifyMinSdkVersionFull(
+ minSdkVersionFullString, Build.VERSION.SDK_INT_FULL, input);
+ if (minSdkVersionFullResult.isError()) {
+ return input.error(minSdkVersionFullResult);
+ }
+ }
+ }
+ }
+
if (isApkInApex) {
val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
if (val != null) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ff08dd27225f..3e2f30118b2a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -67,6 +67,13 @@ interface IStatusBarService
// ---- Methods below are for use by the status bar policy services ----
// You need the STATUS_BAR_SERVICE permission
RegisterStatusBarResult registerStatusBar(IStatusBar callbacks);
+ /**
+ * Registers the status bar for all displays.
+ *
+ * Returns a map of all display IDs (as strings) to their corresponding RegisterStatusBarResult
+ * objects.
+ */
+ Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar callbacks);
void onPanelRevealed(boolean clearNotificationEffects, int numItems);
void onPanelHidden();
// Mark current notifications as "seen" and stop ringing, vibrating, blinking.
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 1e965c5db7ae..bda7547087ae 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import android.ravenwood.annotation.RavenwoodReplace;
import android.util.ArraySet;
import android.util.EmptyArray;
@@ -39,6 +40,10 @@ import java.util.function.IntFunction;
/**
* Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
+ * <p>
+ * Test with:
+ * <code>atest FrameworksUtilTests:com.android.internal.util.ArrayUtilsTest</code>
+ * <code>atest FrameworksUtilTestsRavenwood:com.android.internal.util.ArrayUtilsTest</code>
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ArrayUtils {
@@ -85,6 +90,69 @@ public class ArrayUtils {
}
/**
+ * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This
+ * prevents copies of the data from being left on the Java heap as a result of heap compaction.
+ * Use this when the array will contain sensitive data such as a password or cryptographic key
+ * that needs to be wiped from memory when no longer needed. The owner of the array is still
+ * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so.
+ *
+ * @param length the length of the array to allocate
+ * @return the new array
+ */
+ public static byte[] newNonMovableByteArray(int length) {
+ return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length);
+ }
+
+ /**
+ * Like {@link #newNonMovableByteArray(int)}, but allocates a char array.
+ *
+ * @param length the length of the array to allocate
+ * @return the new array
+ */
+ public static char[] newNonMovableCharArray(int length) {
+ return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length);
+ }
+
+ /**
+ * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive
+ * data such as a password or cryptographic key.
+ * <p>
+ * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler.
+ * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also
+ * in other levels of the memory hierarchy up to and including main memory (but not above that).
+ * <p>
+ * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on
+ * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}.
+ * Use on other arrays might also introduce performance anomalies.
+ *
+ * @param array the array to zeroize. If null, this method has no effect.
+ */
+ @RavenwoodReplace public static native void zeroize(byte[] array);
+
+ /**
+ * Replacement of the above method for host-side unit testing that doesn't support JNI yet.
+ */
+ public static void zeroize$ravenwood(byte[] array) {
+ if (array != null) {
+ Arrays.fill(array, (byte) 0);
+ }
+ }
+
+ /**
+ * Like {@link #zeroize(byte[])}, but for char arrays.
+ */
+ @RavenwoodReplace public static native void zeroize(char[] array);
+
+ /**
+ * Replacement of the above method for host-side unit testing that doesn't support JNI yet.
+ */
+ public static void zeroize$ravenwood(char[] array) {
+ if (array != null) {
+ Arrays.fill(array, (char) 0);
+ }
+ }
+
+ /**
* Checks if the beginnings of two byte arrays are equal.
*
* @param array1 the first byte array
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 754f77e72f8a..d49afa735646 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -435,9 +435,11 @@ public class LatencyTracker {
public void startListeningForLatencyTrackerConfigChanges() {
final Context context = ActivityThread.currentApplication();
if (context == null) {
- if (DEBUG) {
- Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName());
- }
+ Log.e(
+ TAG,
+ String.format(
+ "No application for package: %s. Latency Tracker Disabled",
+ ActivityThread.currentPackageName()));
return;
}
if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java
new file mode 100644
index 000000000000..a090c7abc2db
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INTENSITY;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a basic envelope effect created via
+ * {@link VibrationEffect.BasicEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedBasicEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+ private final BasicControlPoint[] mControlPoints;
+ private final float mInitialSharpness;
+
+ SerializedBasicEnvelopeEffect(BasicControlPoint[] controlPoints, float initialSharpness) {
+ mControlPoints = controlPoints;
+ mInitialSharpness = initialSharpness;
+ }
+
+ @Override
+ public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+
+ if (!Float.isNaN(mInitialSharpness)) {
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_SHARPNESS, mInitialSharpness);
+ }
+
+ for (BasicControlPoint point : mControlPoints) {
+ serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INTENSITY, point.mIntensity);
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_SHARPNESS, point.mSharpness);
+ serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+ serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+ }
+
+ serializer.endTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+ }
+
+ @Override
+ public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+ VibrationEffect.BasicEnvelopeBuilder builder = new VibrationEffect.BasicEnvelopeBuilder();
+
+ if (!Float.isNaN(mInitialSharpness)) {
+ builder.setInitialSharpness(mInitialSharpness);
+ }
+
+ for (BasicControlPoint point : mControlPoints) {
+ builder.addControlPoint(point.mIntensity, point.mSharpness, point.mDurationMs);
+ }
+ composition.addEffect(builder.build());
+ }
+
+ @Override
+ public String toString() {
+ return "SerializedBasicEnvelopeEffect{"
+ + "initialSharpness=" + (Float.isNaN(mInitialSharpness) ? "" : mInitialSharpness)
+ + ", controlPoints=" + Arrays.toString(mControlPoints)
+ + '}';
+ }
+
+ static final class Builder {
+ private final List<BasicControlPoint> mControlPoints;
+ private float mInitialSharpness = Float.NaN;
+
+ Builder() {
+ mControlPoints = new ArrayList<>();
+ }
+
+ void setInitialSharpness(float sharpness) {
+ mInitialSharpness = sharpness;
+ }
+
+ void addControlPoint(float intensity, float sharpness, long durationMs) {
+ mControlPoints.add(new BasicControlPoint(intensity, sharpness, durationMs));
+ }
+
+ SerializedBasicEnvelopeEffect build() {
+ return new SerializedBasicEnvelopeEffect(
+ mControlPoints.toArray(new BasicControlPoint[0]), mInitialSharpness);
+ }
+ }
+
+ /** Parser implementation for {@link SerializedBasicEnvelopeEffect}. */
+ static final class Parser {
+
+ @NonNull
+ static SerializedBasicEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+ @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, TAG_BASIC_ENVELOPE_EFFECT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_SHARPNESS);
+
+ Builder builder = new Builder();
+ builder.setInitialSharpness(
+ XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INITIAL_SHARPNESS, 0f, 1f,
+ Float.NaN));
+
+ int outerDepth = parser.getDepth();
+
+ // Read all nested tags
+ while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+ parseControlPoint(parser, builder);
+ // Consume tag
+ XmlReader.readEndTag(parser);
+ }
+
+ // Check schema assertions about <basic-envelope-effect>
+ XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+ "Expected tag %s to have at least one control point",
+ TAG_BASIC_ENVELOPE_EFFECT);
+ XmlValidator.checkParserCondition(builder.mControlPoints.getLast().mIntensity == 0,
+ "Basic envelope effects must end at a zero intensity control point");
+
+ return builder.build();
+ }
+
+ private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+ throws XmlParserException {
+ XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(
+ parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_INTENSITY,
+ ATTRIBUTE_SHARPNESS);
+ float intensity = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INTENSITY, 0,
+ 1);
+ float sharpness = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_SHARPNESS, 0,
+ 1);
+ long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+ builder.addControlPoint(intensity, sharpness, durationMs);
+ }
+ }
+
+ private static final class BasicControlPoint {
+ private final float mIntensity;
+ private final float mSharpness;
+ private final long mDurationMs;
+
+ BasicControlPoint(float intensity, float sharpness, long durationMs) {
+ mIntensity = intensity;
+ mSharpness = sharpness;
+ mDurationMs = durationMs;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mIntensity, mSharpness,
+ mDurationMs);
+ }
+ }
+}
+
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
new file mode 100644
index 000000000000..6a893430d7ad
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a waveform envelope effect created via
+ * {@link VibrationEffect.WaveformEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedWaveformEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+
+ private final WaveformControlPoint[] mControlPoints;
+ private final float mInitialFrequency;
+
+ SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency) {
+ mControlPoints = controlPoints;
+ mInitialFrequency = initialFrequency;
+ }
+
+ @Override
+ public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+ if (!Float.isNaN(mInitialFrequency)) {
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_FREQUENCY_HZ, mInitialFrequency);
+ }
+
+ for (WaveformControlPoint point : mControlPoints) {
+ serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_AMPLITUDE, point.mAmplitude);
+ serializer.attributeFloat(NAMESPACE, ATTRIBUTE_FREQUENCY_HZ, point.mFrequency);
+ serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+ serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+ }
+
+ serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+ }
+
+ @Override
+ public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+ VibrationEffect.WaveformEnvelopeBuilder builder =
+ new VibrationEffect.WaveformEnvelopeBuilder();
+
+ if (!Float.isNaN(mInitialFrequency)) {
+ builder.setInitialFrequencyHz(mInitialFrequency);
+ }
+
+ for (WaveformControlPoint point : mControlPoints) {
+ builder.addControlPoint(point.mAmplitude, point.mFrequency, point.mDurationMs);
+ }
+ composition.addEffect(builder.build());
+ }
+
+ @Override
+ public String toString() {
+ return "SerializedWaveformEnvelopeEffect{"
+ + "InitialFrequency=" + (Float.isNaN(mInitialFrequency) ? "" : mInitialFrequency)
+ + ", controlPoints=" + Arrays.toString(mControlPoints)
+ + '}';
+ }
+
+ static final class Builder {
+ private final List<WaveformControlPoint> mControlPoints;
+ private float mInitialFrequencyHz = Float.NaN;
+
+ Builder() {
+ mControlPoints = new ArrayList<>();
+ }
+
+ void setInitialFrequencyHz(float frequencyHz) {
+ mInitialFrequencyHz = frequencyHz;
+ }
+
+ void addControlPoint(float amplitude, float frequencyHz, long durationMs) {
+ mControlPoints.add(new WaveformControlPoint(amplitude, frequencyHz, durationMs));
+ }
+
+ SerializedWaveformEnvelopeEffect build() {
+ return new SerializedWaveformEnvelopeEffect(
+ mControlPoints.toArray(new WaveformControlPoint[0]), mInitialFrequencyHz);
+ }
+ }
+
+ /** Parser implementation for {@link SerializedWaveformEnvelopeEffect}. */
+ static final class Parser {
+
+ @NonNull
+ static SerializedWaveformEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+ @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENVELOPE_EFFECT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ);
+
+ Builder builder = new Builder();
+ builder.setInitialFrequencyHz(
+ XmlReader.readAttributePositiveFloat(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ,
+ Float.NaN));
+
+ int outerDepth = parser.getDepth();
+
+ while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+ parseControlPoint(parser, builder);
+ // Consume tag
+ XmlReader.readEndTag(parser);
+ }
+
+ // Check schema assertions about <waveform-envelope-effect>
+ XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+ "Expected tag %s to have at least one control point",
+ TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+ return builder.build();
+ }
+
+ private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+ throws XmlParserException {
+ XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(
+ parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE,
+ ATTRIBUTE_FREQUENCY_HZ);
+ float amplitude = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_AMPLITUDE, 0,
+ 1);
+ float frequencyHz = XmlReader.readAttributePositiveFloat(parser,
+ ATTRIBUTE_FREQUENCY_HZ);
+ long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+ builder.addControlPoint(amplitude, frequencyHz, durationMs);
+ }
+ }
+
+ private static final class WaveformControlPoint {
+ private final float mAmplitude;
+ private final float mFrequency;
+ private final long mDurationMs;
+
+ WaveformControlPoint(float amplitude, float frequency, long durationMs) {
+ mAmplitude = amplitude;
+ mFrequency = frequency;
+ mDurationMs = durationMs;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mAmplitude, mFrequency,
+ mDurationMs);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index a9fbcafa128d..314bfe40ee0b 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -16,11 +16,13 @@
package com.android.internal.vibrator.persistence;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
import android.annotation.NonNull;
import android.os.VibrationEffect;
@@ -92,6 +94,32 @@ import java.util.List;
* }
* </pre>
*
+ * * Waveform Envelope effects
+ *
+ * <pre>
+ * {@code
+ * <vibration-effect>
+ * <waveform-envelope-effect initialFrequencyHz="20.0">
+ * <control-point amplitude="0.2" frequencyHz="80.0" durationMs="50" />
+ * <control-point amplitude="0.5" frequencyHz="150.0" durationMs="50" />
+ * </envelope-effect>
+ * </vibration-effect>
+ * }
+ * </pre>
+ *
+ * * Basic Envelope effects
+ *
+ * <pre>
+ * {@code
+ * <vibration-effect>
+ * <basic-envelope-effect initialSharpness="0.3">
+ * <control-point intensity="0.2" sharpness="0.5" durationMs="50" />
+ * <control-point intensity="0.0" sharpness="1.0" durationMs="50" />
+ * </envelope-effect>
+ * </vibration-effect>
+ * }
+ * </pre>
+ *
* @hide
*/
public class VibrationEffectXmlParser {
@@ -151,6 +179,18 @@ public class VibrationEffectXmlParser {
serializedVibration = new SerializedComposedEffect(
SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
break;
+ case TAG_WAVEFORM_ENVELOPE_EFFECT:
+ if (Flags.normalizedPwleEffects()) {
+ serializedVibration = new SerializedComposedEffect(
+ SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags));
+ break;
+ } // else fall through
+ case TAG_BASIC_ENVELOPE_EFFECT:
+ if (Flags.normalizedPwleEffects()) {
+ serializedVibration = new SerializedComposedEffect(
+ SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags));
+ break;
+ } // else fall through
default:
throw new XmlParserException("Unexpected tag " + parser.getName()
+ " in vibration tag " + vibrationTagName);
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
index cb834a5eac7e..ebe34344c6f5 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -19,9 +19,11 @@ package com.android.internal.vibrator.persistence;
import android.annotation.NonNull;
import android.os.PersistableBundle;
import android.os.VibrationEffect;
+import android.os.vibrator.BasicPwleSegment;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
@@ -45,6 +47,8 @@ import java.util.List;
* <li>A composition created exclusively via
* {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
* <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
+ * <li>{@link VibrationEffect.WaveformEnvelopeBuilder}
+ * <li>{@link VibrationEffect.BasicEnvelopeBuilder}
* </ul>
*
* @hide
@@ -77,6 +81,12 @@ public final class VibrationEffectXmlSerializer {
if (firstSegment instanceof PrimitiveSegment) {
return serializePrimitiveEffect(composed);
}
+ if (Flags.normalizedPwleEffects() && firstSegment instanceof PwleSegment) {
+ return serializeWaveformEnvelopeEffect(composed);
+ }
+ if (Flags.normalizedPwleEffects() && firstSegment instanceof BasicPwleSegment) {
+ return serializeBasicEnvelopeEffect(composed);
+ }
return serializeWaveformEffect(composed);
}
@@ -110,6 +120,53 @@ public final class VibrationEffectXmlSerializer {
return new SerializedComposedEffect(primitives);
}
+ private static SerializedComposedEffect serializeWaveformEnvelopeEffect(
+ VibrationEffect.Composed effect) throws XmlSerializerException {
+ SerializedWaveformEnvelopeEffect.Builder builder =
+ new SerializedWaveformEnvelopeEffect.Builder();
+ List<VibrationEffectSegment> segments = effect.getSegments();
+ XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+ "Unsupported repeating waveform envelope effect %s", effect);
+ for (int i = 0; i < segments.size(); i++) {
+ XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment,
+ "Unsupported segment for waveform envelope effect %s", segments.get(i));
+ PwleSegment segment = (PwleSegment) segments.get(i);
+
+ if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) {
+ // Initial frequency explicitly defined.
+ builder.setInitialFrequencyHz(segment.getStartFrequencyHz());
+ }
+
+ builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(),
+ segment.getDuration());
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static SerializedComposedEffect serializeBasicEnvelopeEffect(
+ VibrationEffect.Composed effect) throws XmlSerializerException {
+ SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder();
+ List<VibrationEffectSegment> segments = effect.getSegments();
+ XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+ "Unsupported repeating basic envelope effect %s", effect);
+ for (int i = 0; i < segments.size(); i++) {
+ XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment,
+ "Unsupported segment for basic envelope effect %s", segments.get(i));
+ BasicPwleSegment segment = (BasicPwleSegment) segments.get(i);
+
+ if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) {
+ // Initial sharpness explicitly defined.
+ builder.setInitialSharpness(segment.getStartSharpness());
+ }
+
+ builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(),
+ segment.getDuration());
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
private static SerializedComposedEffect serializeWaveformEffect(
VibrationEffect.Composed effect) throws XmlSerializerException {
SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index 4122215a2b04..df262cfecd5a 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -42,14 +42,22 @@ public final class XmlConstants {
public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
public static final String TAG_VENDOR_EFFECT = "vendor-effect";
+ public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect";
+ public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect";
public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
public static final String TAG_REPEATING = "repeating";
+ public static final String TAG_CONTROL_POINT = "control-point";
public static final String ATTRIBUTE_NAME = "name";
public static final String ATTRIBUTE_FALLBACK = "fallback";
public static final String ATTRIBUTE_DURATION_MS = "durationMs";
public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
+ public static final String ATTRIBUTE_FREQUENCY_HZ = "frequencyHz";
+ public static final String ATTRIBUTE_INITIAL_FREQUENCY_HZ = "initialFrequencyHz";
+ public static final String ATTRIBUTE_INTENSITY = "intensity";
+ public static final String ATTRIBUTE_SHARPNESS = "sharpness";
+ public static final String ATTRIBUTE_INITIAL_SHARPNESS = "initialSharpness";
public static final String ATTRIBUTE_SCALE = "scale";
public static final String ATTRIBUTE_DELAY_MS = "delayMs";
public static final String ATTRIBUTE_DELAY_TYPE = "delayType";
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index 0ac6fefc8cb2..1c4a783f0fe4 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -221,12 +221,63 @@ public final class XmlReader {
if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
return defaultValue;
}
+
+ return readAttributeFloatInRange(parser, attrName, lowerInclusive, upperInclusive);
+ }
+
+ /**
+ * Read attribute from current tag as a float within given inclusive range.
+ */
+ public static float readAttributeFloatInRange(
+ TypedXmlPullParser parser, String attrName, float lowerInclusive,
+ float upperInclusive) throws XmlParserException {
String tagName = parser.getName();
float value = readAttributeFloat(parser, attrName);
XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive,
- "Unexpected %s = %f in tag %s, expected %s in [%f, %f]",
- attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
+ "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", attrName, value, tagName,
+ attrName, lowerInclusive, upperInclusive);
+ return value;
+ }
+
+ /**
+ * Read attribute from current tag as a positive float, returning default value if attribute
+ * is missing.
+ */
+ public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName,
+ float defaultValue) throws XmlParserException {
+ if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
+ return defaultValue;
+ }
+
+ return readAttributePositiveFloat(parser, attrName);
+ }
+
+ /**
+ * Read attribute from current tag as a positive float.
+ */
+ public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName)
+ throws XmlParserException {
+ String tagName = parser.getName();
+ float value = readAttributeFloat(parser, attrName);
+
+ XmlValidator.checkParserCondition(value > 0,
+ "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+ attrName);
+ return value;
+ }
+
+ /**
+ * Read attribute from current tag as a positive long.
+ */
+ public static long readAttributePositiveLong(TypedXmlPullParser parser, String attrName)
+ throws XmlParserException {
+ String tagName = parser.getName();
+ long value = readAttributeLong(parser, attrName);
+
+ XmlValidator.checkParserCondition(value > 0,
+ "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+ attrName);
return value;
}
@@ -251,4 +302,15 @@ public final class XmlReader {
throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
}
}
+
+ private static long readAttributeLong(TypedXmlPullParser parser, String attrName)
+ throws XmlParserException {
+ String tagName = parser.getName();
+ try {
+ return parser.getAttributeLong(NAMESPACE, attrName);
+ } catch (XmlPullParserException e) {
+ String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
+ throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 2bfbf843b2ab..4b90420a75ee 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import static android.app.Flags.notificationsRedesignTemplates;
import static android.widget.flags.Flags.conversationLayoutUseMaximumChildHeight;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
@@ -692,7 +693,7 @@ public class ConversationLayout extends FrameLayout
}
private void updateActionListPadding() {
- if (mActions != null) {
+ if (!notificationsRedesignTemplates() && mActions != null) {
mActions.setCollapsibleIndentDimen(R.dimen.call_notification_collapsible_indent);
}
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 97a2d3beda90..4305ba753e46 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -261,7 +261,7 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
MessagingGroup createdGroup = sInstancePool.acquire();
if (createdGroup == null) {
createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
- R.layout.notification_template_messaging_group, layout,
+ getMessagingGroupLayoutResource(), layout,
false);
createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
@@ -269,6 +269,14 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
return createdGroup;
}
+ private static int getMessagingGroupLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_messaging_group;
+ } else {
+ return R.layout.notification_template_messaging_group;
+ }
+ }
+
public void removeMessage(MessagingMessage messagingMessage,
ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) {
View view = messagingMessage.getView();
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 301dc392c125..cac2024f548d 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -20,6 +20,7 @@ import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import android.annotation.DimenRes;
+import android.app.Flags;
import android.app.Notification;
import android.content.Context;
import android.content.res.TypedArray;
@@ -58,7 +59,7 @@ public class NotificationActionListLayout extends LinearLayout {
private int mEmphasizedPaddingBottom;
private int mEmphasizedHeight;
private int mRegularHeight;
- @DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+ @DimenRes private int mCollapsibleIndentDimen;
int mNumNotGoneChildren;
int mNumPriorityChildren;
@@ -73,6 +74,10 @@ public class NotificationActionListLayout extends LinearLayout {
public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mCollapsibleIndentDimen = Flags.notificationsRedesignTemplates()
+ ? R.dimen.notification_2025_actions_margin_start
+ : R.dimen.notification_actions_padding_start;
+
int[] attrIds = { android.R.attr.gravity };
TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
mGravity = ta.getInt(0, 0);
diff --git a/core/java/com/android/internal/widget/flags.aconfig b/core/java/com/android/internal/widget/flags.aconfig
new file mode 100644
index 000000000000..f05aa4f460a5
--- /dev/null
+++ b/core/java/com/android/internal/widget/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.widget.flags"
+container: "system"
+
+flag {
+ name: "hide_last_char_with_physical_input"
+ namespace: "input"
+ description: "Feature flag for changing the default of hiding the last interacted symbol when a physical input device is present"
+ bug: "339270220"
+}
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 38685b652c50..5177a0360032 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -435,10 +435,15 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup {
private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
refreshViewPort();
- // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
- // landscape.
- final int x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
- mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
+ final int x;
+ if (mPopupWindow.getWidth() > mViewPortOnScreen.width()) {
+ // Not enough space - prefer to position as far left as possible
+ x = mViewPortOnScreen.left;
+ } else {
+ // Initialize x ensuring that the toolbar isn't rendered behind the system bar insets
+ x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+ mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
+ }
final int y;
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
new file mode 100644
index 000000000000..1bdbaa48d18c
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+public class AndroidPlatformSemanticNodeApplier
+ extends BaseSemanticNodeApplier<AccessibilityNodeInfo> {
+
+ private static final String ROLE_DESCRIPTION_KEY = "AccessibilityNodeInfo.roleDescription";
+
+ @Override
+ protected void setClickable(AccessibilityNodeInfo nodeInfo, boolean clickable) {
+ nodeInfo.setClickable(clickable);
+ if (clickable) {
+ nodeInfo.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+ } else {
+ nodeInfo.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+ }
+ }
+
+ @Override
+ protected void setEnabled(AccessibilityNodeInfo nodeInfo, boolean enabled) {
+ nodeInfo.setEnabled(enabled);
+ }
+
+ @Override
+ protected CharSequence getStateDescription(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.getStateDescription();
+ }
+
+ @Override
+ protected void setStateDescription(AccessibilityNodeInfo nodeInfo, CharSequence description) {
+ nodeInfo.setStateDescription(description);
+ }
+
+ @Override
+ protected void setRoleDescription(AccessibilityNodeInfo nodeInfo, String description) {
+ nodeInfo.getExtras().putCharSequence(ROLE_DESCRIPTION_KEY, description);
+ }
+
+ @Override
+ protected CharSequence getText(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.getText();
+ }
+
+ @Override
+ protected void setText(AccessibilityNodeInfo nodeInfo, CharSequence text) {
+ nodeInfo.setText(text);
+ }
+
+ @Override
+ protected CharSequence getContentDescription(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.getContentDescription();
+ }
+
+ @Override
+ protected void setContentDescription(AccessibilityNodeInfo nodeInfo, CharSequence description) {
+ nodeInfo.setContentDescription(description);
+ }
+
+ @Override
+ protected void setBoundsInScreen(AccessibilityNodeInfo nodeInfo, Rect bounds) {
+ nodeInfo.setBoundsInParent(bounds);
+ nodeInfo.setBoundsInScreen(bounds);
+ }
+
+ @Override
+ protected void setUniqueId(AccessibilityNodeInfo nodeInfo, String id) {
+ nodeInfo.setUniqueId(id);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java
new file mode 100644
index 000000000000..228afb88b5de
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+import android.graphics.Rect;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+/**
+ * Base class for applying semantic information to a node.
+ *
+ * <p>This class provides common functionality for applying semantic information extracted from
+ * Compose UI components to a node representation used for accessibility purposes. It handles
+ * applying properties like content description, text, role, clickability, and bounds.
+ *
+ * <p>Subclasses are responsible for implementing methods to actually set these properties on the
+ * specific node type they handle.
+ *
+ * @param <N> The type of node this applier works with.
+ */
+public abstract class BaseSemanticNodeApplier<N> implements SemanticNodeApplier<N> {
+ @Override
+ public void applyComponent(
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+ N nodeInfo,
+ Component component,
+ List<AccessibilitySemantics> semantics) {
+ float[] locationInWindow = new float[2];
+ component.getLocationInWindow(locationInWindow);
+ Rect bounds =
+ new Rect(
+ (int) locationInWindow[0],
+ (int) locationInWindow[1],
+ (int) (locationInWindow[0] + component.getWidth()),
+ (int) (locationInWindow[1] + component.getHeight()));
+ setBoundsInScreen(nodeInfo, bounds);
+
+ setUniqueId(nodeInfo, String.valueOf(component.getComponentId()));
+
+ if (component instanceof AccessibleComponent) {
+ applyContentDescription(
+ ((AccessibleComponent) component).getContentDescriptionId(),
+ nodeInfo,
+ remoteComposeAccessibility);
+
+ applyText(
+ ((AccessibleComponent) component).getTextId(),
+ nodeInfo,
+ remoteComposeAccessibility);
+
+ applyRole(((AccessibleComponent) component).getRole(), nodeInfo);
+ }
+
+ applySemantics(remoteComposeAccessibility, nodeInfo, semantics);
+
+ if (getText(nodeInfo) == null && getContentDescription(nodeInfo) == null) {
+ setContentDescription(nodeInfo, "");
+ }
+ }
+
+ protected void applySemantics(
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+ N nodeInfo,
+ List<AccessibilitySemantics> semantics) {
+ for (AccessibilitySemantics semantic : semantics) {
+ if (semantic.isInterestingForSemantics()) {
+ if (semantic instanceof CoreSemantics) {
+ CoreSemantics coreSemantics = (CoreSemantics) semantic;
+ applyCoreSemantics(remoteComposeAccessibility, nodeInfo, coreSemantics);
+ } else if (semantic instanceof AccessibleComponent) {
+ AccessibleComponent accessibleComponent = (AccessibleComponent) semantic;
+ if (accessibleComponent.isClickable()) {
+ setClickable(nodeInfo, true);
+ }
+
+ if (accessibleComponent.getContentDescriptionId() != null) {
+ applyContentDescription(
+ accessibleComponent.getContentDescriptionId(),
+ nodeInfo,
+ remoteComposeAccessibility);
+ }
+
+ if (accessibleComponent.getTextId() != null) {
+ applyText(
+ accessibleComponent.getTextId(),
+ nodeInfo,
+ remoteComposeAccessibility);
+ }
+
+ applyRole(accessibleComponent.getRole(), nodeInfo);
+ }
+ }
+ }
+ }
+
+ protected void applyCoreSemantics(
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+ N nodeInfo,
+ CoreSemantics coreSemantics) {
+ applyContentDescription(
+ coreSemantics.getContentDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+ applyRole(coreSemantics.getRole(), nodeInfo);
+
+ applyText(coreSemantics.getTextId(), nodeInfo, remoteComposeAccessibility);
+
+ applyStateDescription(
+ coreSemantics.getStateDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+ if (!coreSemantics.mEnabled) {
+ setEnabled(nodeInfo, false);
+ }
+ }
+
+ protected void applyStateDescription(
+ Integer stateDescriptionId,
+ N nodeInfo,
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+ if (stateDescriptionId != null) {
+ setStateDescription(
+ nodeInfo,
+ appendNullable(
+ getStateDescription(nodeInfo),
+ remoteComposeAccessibility.stringValue(stateDescriptionId)));
+ }
+ }
+
+ protected void applyRole(AccessibleComponent.Role role, N nodeInfo) {
+ if (role != null) {
+ setRoleDescription(nodeInfo, role.getDescription());
+ }
+ }
+
+ protected void applyText(
+ Integer textId,
+ N nodeInfo,
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+ if (textId != null) {
+ setText(
+ nodeInfo,
+ appendNullable(
+ getText(nodeInfo), remoteComposeAccessibility.stringValue(textId)));
+ }
+ }
+
+ protected void applyContentDescription(
+ Integer contentDescriptionId,
+ N nodeInfo,
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+ if (contentDescriptionId != null) {
+ setContentDescription(
+ nodeInfo,
+ appendNullable(
+ getContentDescription(nodeInfo),
+ remoteComposeAccessibility.stringValue(contentDescriptionId)));
+ }
+ }
+
+ private CharSequence appendNullable(CharSequence contentDescription, String value) {
+ if (contentDescription == null) {
+ return value;
+ } else if (value == null) {
+ return contentDescription;
+ } else {
+ return contentDescription + " " + value;
+ }
+ }
+
+ protected abstract void setClickable(N nodeInfo, boolean b);
+
+ protected abstract void setEnabled(N nodeInfo, boolean b);
+
+ protected abstract CharSequence getStateDescription(N nodeInfo);
+
+ protected abstract void setStateDescription(N nodeInfo, CharSequence charSequence);
+
+ protected abstract void setRoleDescription(N nodeInfo, String description);
+
+ protected abstract CharSequence getText(N nodeInfo);
+
+ protected abstract void setText(N nodeInfo, CharSequence charSequence);
+
+ protected abstract CharSequence getContentDescription(N nodeInfo);
+
+ protected abstract void setContentDescription(N nodeInfo, CharSequence charSequence);
+
+ protected abstract void setBoundsInScreen(N nodeInfo, Rect bounds);
+
+ protected abstract void setUniqueId(N nodeInfo, String s);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
new file mode 100644
index 000000000000..2cd4f0362306
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
+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.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Java Player implementation of the {@link RemoteComposeDocumentAccessibility} interface. Each item
+ * in the semantic tree is a {@link Component} from the remote Compose UI. Each Component can have a
+ * list of modifiers that must be tagged with {@link AccessibilitySemantics} either incidentally
+ * (see {@link ClickModifierOperation}) or explicitly (see {@link CoreSemantics}).
+ */
+public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibility {
+ private final CoreDocument mDocument;
+
+ public CoreDocumentAccessibility(CoreDocument document) {
+ this.mDocument = document;
+ }
+
+ @Nullable
+ @Override
+ public Integer getComponentIdAt(PointF point) {
+ return RootId;
+ }
+
+ @Override
+ public @Nullable Component findComponentById(int virtualViewId) {
+ RootLayoutComponent root = mDocument.getRootLayoutComponent();
+
+ if (root == null || virtualViewId == -1) {
+ return root;
+ }
+
+ return componentStream(root)
+ .filter(op -> op.getComponentId() == virtualViewId)
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Override
+ public CoreSemantics.Mode mergeMode(Component component) {
+ if (!(component instanceof LayoutComponent)) {
+ return CoreSemantics.Mode.SET;
+ }
+
+ CoreSemantics.Mode result = CoreSemantics.Mode.SET;
+
+ for (ModifierOperation modifier :
+ ((LayoutComponent) component).getComponentModifiers().getList()) {
+ if (modifier instanceof AccessibleComponent) {
+ AccessibleComponent semantics = (AccessibleComponent) modifier;
+
+ if (semantics.getMode().ordinal() > result.ordinal()) {
+ result = semantics.getMode();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean performAction(Component component, int action, Bundle arguments) {
+ if (action == ACTION_CLICK) {
+ mDocument.performClick(component.getComponentId());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Nullable
+ @Override
+ public String stringValue(int id) {
+ Object value = mDocument.getRemoteComposeState().getFromId(id);
+
+ return value != null ? String.valueOf(value) : null;
+ }
+
+ @Override
+ public List<AccessibilitySemantics> semanticModifiersForComponent(Component component) {
+ if (!(component instanceof LayoutComponent)) {
+ return Collections.emptyList();
+ }
+
+ List<ModifierOperation> modifiers =
+ ((LayoutComponent) component).getComponentModifiers().getList();
+
+ return modifiers.stream()
+ .filter(
+ it ->
+ it instanceof AccessibilitySemantics
+ && ((AccessibilitySemantics) it)
+ .isInterestingForSemantics())
+ .map(i -> (AccessibilitySemantics) i)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<Integer> semanticallyRelevantChildComponents(
+ Component component, boolean useUnmergedTree) {
+ if (!component.isVisible()) {
+ return Collections.emptyList();
+ }
+
+ CoreSemantics.Mode mergeMode = mergeMode(component);
+ if (mergeMode == CoreSemantics.Mode.CLEAR_AND_SET
+ || (!useUnmergedTree && mergeMode == CoreSemantics.Mode.MERGE)) {
+ return Collections.emptyList();
+ }
+
+ ArrayList<Integer> result = new ArrayList<>();
+
+ for (Operation child : component.mList) {
+ if (child instanceof Component) {
+ if (isInteresting((Component) child)) {
+ result.add(((Component) child).getComponentId());
+ } else {
+ result.addAll(
+ semanticallyRelevantChildComponents(
+ (Component) child, useUnmergedTree));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static Stream<Component> componentStream(Component root) {
+ return Stream.concat(
+ Stream.of(root),
+ root.mList.stream()
+ .flatMap(
+ op -> {
+ if (op instanceof Component) {
+ return componentStream((Component) op);
+ } else {
+ return Stream.empty();
+ }
+ }));
+ }
+
+ static Stream<ModifierOperation> modifiersStream(Component component) {
+ return component.mList.stream()
+ .filter(it -> it instanceof ComponentModifiers)
+ .flatMap(it -> ((ComponentModifiers) it).getList().stream());
+ }
+
+ static boolean isInteresting(Component component) {
+ if (!component.isVisible()) {
+ return false;
+ }
+
+ return isContainerWithSemantics(component)
+ || modifiersStream(component)
+ .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+ }
+
+ static boolean isModifierWithSemantics(ModifierOperation modifier) {
+ return modifier instanceof AccessibilitySemantics
+ && ((AccessibilitySemantics) modifier).isInterestingForSemantics();
+ }
+
+ static boolean isContainerWithSemantics(Component component) {
+ if (component instanceof AccessibilitySemantics) {
+ return ((AccessibilitySemantics) component).isInterestingForSemantics();
+ }
+
+ if (!(component instanceof LayoutComponent)) {
+ return false;
+ }
+
+ return ((LayoutComponent) component)
+ .getComponentModifiers().getList().stream()
+ .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
new file mode 100644
index 000000000000..010253e9cb95
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+
+/**
+ * Trivial wrapper for calling setAccessibilityDelegate on a View. This exists primarily because the
+ * RemoteDocumentPlayer is either running in the platform on a known API version, or outside in
+ * which case it must use the Androidx ViewCompat class.
+ */
+public class PlatformRemoteComposeAccessibilityRegistrar
+ implements RemoteComposeAccessibilityRegistrar {
+ public PlatformRemoteComposeTouchHelper forRemoteComposePlayer(
+ View player, @NonNull CoreDocument coreDocument) {
+ return new PlatformRemoteComposeTouchHelper(
+ player,
+ new CoreDocumentAccessibility(coreDocument),
+ new AndroidPlatformSemanticNodeApplier());
+ }
+
+ public void setAccessibilityDelegate(View remoteComposePlayer, CoreDocument document) {
+ remoteComposePlayer.setAccessibilityDelegate(
+ forRemoteComposePlayer(remoteComposePlayer, document));
+ }
+
+ public void clearAccessibilityDelegate(View remoteComposePlayer) {
+ remoteComposePlayer.setAccessibilityDelegate(null);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
new file mode 100644
index 000000000000..39a2ab3010ac
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import static com.android.internal.widget.remotecompose.accessibility.RemoteComposeDocumentAccessibility.RootId;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.util.IntArray;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.widget.ExploreByTouchHelper;
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics.Mode;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper {
+ private final RemoteComposeDocumentAccessibility mRemoteDocA11y;
+
+ private final SemanticNodeApplier<AccessibilityNodeInfo> mApplier;
+
+ public PlatformRemoteComposeTouchHelper(
+ View host,
+ RemoteComposeDocumentAccessibility remoteDocA11y,
+ SemanticNodeApplier<AccessibilityNodeInfo> applier) {
+ super(host);
+ this.mRemoteDocA11y = remoteDocA11y;
+ this.mApplier = applier;
+ }
+
+ public static PlatformRemoteComposeTouchHelper forRemoteComposePlayer(
+ View player, @NonNull CoreDocument coreDocument) {
+ return new PlatformRemoteComposeTouchHelper(
+ player,
+ new CoreDocumentAccessibility(coreDocument),
+ new AndroidPlatformSemanticNodeApplier());
+ }
+
+ /**
+ * Gets the virtual view ID at a given location on the screen.
+ *
+ * <p>This method is called by the Accessibility framework to determine which virtual view, if
+ * any, is located at a specific point on the screen. It uses the {@link
+ * RemoteComposeDocumentAccessibility#getComponentIdAt(PointF)} method to find the ID of the
+ * component at the given coordinates.
+ *
+ * @param x The x-coordinate of the location in pixels.
+ * @param y The y-coordinate of the location in pixels.
+ * @return The ID of the virtual view at the given location, or {@link #INVALID_ID} if no
+ * virtual view is found at that location.
+ */
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ Integer root = mRemoteDocA11y.getComponentIdAt(new PointF(x, y));
+
+ if (root == null) {
+ return INVALID_ID;
+ }
+
+ return root;
+ }
+
+ /**
+ * Populates a list with the visible virtual view IDs.
+ *
+ * <p>This method is called by the accessibility framework to retrieve the IDs of all visible
+ * virtual views in the accessibility hierarchy. It traverses the hierarchy starting from the
+ * root node (RootId) and adds the ID of each visible view to the provided list.
+ *
+ * @param virtualViewIds The list to be populated with the visible virtual view IDs.
+ */
+ @Override
+ protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+ Stack<Integer> toVisit = new Stack<>();
+ Set<Integer> visited = new HashSet<>();
+
+ toVisit.push(RootId);
+
+ while (!toVisit.isEmpty()) {
+ Integer componentId = toVisit.remove(0);
+
+ if (visited.add(componentId)) {
+ Component component = mRemoteDocA11y.findComponentById(componentId);
+
+ // Only include the root when it has semantics such as content description
+ if (!RootId.equals(componentId)
+ || !mRemoteDocA11y.semanticModifiersForComponent(component).isEmpty()) {
+ virtualViewIds.add(componentId);
+ }
+
+ if (component != null) {
+ Mode mergeMode = mRemoteDocA11y.mergeMode(component);
+
+ if (mergeMode == Mode.SET) {
+ List<Integer> childViews =
+ mRemoteDocA11y.semanticallyRelevantChildComponents(
+ component, false);
+
+ toVisit.addAll(childViews);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onPopulateNodeForVirtualView(
+ int virtualViewId, @NonNull AccessibilityNodeInfo node) {
+ Component component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+ Mode mergeMode = mRemoteDocA11y.mergeMode(component);
+
+ // default to enabled
+ node.setEnabled(true);
+
+ if (mergeMode == Mode.MERGE) {
+ List<Integer> childViews =
+ mRemoteDocA11y.semanticallyRelevantChildComponents(component, true);
+
+ for (Integer childView : childViews) {
+ onPopulateNodeForVirtualView(childView, node);
+ }
+ }
+
+ List<AccessibilitySemantics> semantics =
+ mRemoteDocA11y.semanticModifiersForComponent(component);
+ mApplier.applyComponent(mRemoteDocA11y, node, component, semantics);
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {}
+
+ @Override
+ protected boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, @Nullable Bundle arguments) {
+ Component component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+ if (component != null) {
+ return mRemoteDocA11y.performAction(component, action, arguments);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java
new file mode 100644
index 000000000000..7e8236b35e97
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+
+/**
+ * Interface for registering and clearing accessibility delegates for remote compose players.
+ *
+ * <p>This interface is responsible for managing the accessibility delegate associated with a remote
+ * compose player view. It allows for setting and clearing the delegate, which is used to handle
+ * accessibility events and provide accessibility information for the remote compose content.
+ */
+public interface RemoteComposeAccessibilityRegistrar {
+ void setAccessibilityDelegate(View remoteComposePlayer, CoreDocument document);
+
+ void clearAccessibilityDelegate(View remoteComposePlayer);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
new file mode 100644
index 000000000000..50f75e4889dd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+/**
+ * Interface for interacting with the accessibility features of a remote Compose UI. This interface
+ * provides methods to perform actions, retrieve state, and query the accessibility tree of the
+ * remote Compose UI.
+ */
+public interface RemoteComposeDocumentAccessibility {
+ // Matches ExploreByTouchHelper.HOST_ID
+ Integer RootId = View.NO_ID;
+
+ // androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK
+ int ACTION_CLICK = 0x00000010;
+
+ /**
+ * Performs the specified action on the given component.
+ *
+ * @param component The component on which to perform the action.
+ * @param action The action to perform.
+ * @param arguments Optional arguments for the action.
+ * @return {@code true} if the action was performed successfully, {@code false} otherwise.
+ */
+ boolean performAction(Component component, int action, Bundle arguments);
+
+ /**
+ * Retrieves the string value associated with the given ID.
+ *
+ * @param id The ID to retrieve the string value for.
+ * @return The string value associated with the ID, or {@code null} if no such value exists.
+ */
+ @Nullable
+ String stringValue(int id);
+
+ /**
+ * Retrieves a list of child view IDs semantically contained within the given component/virtual
+ * view. These may later be hidden from accessibility services by properties, but should contain
+ * only possibly semantically relevant virtual views.
+ *
+ * @param component The component to retrieve child view IDs from, or [RootId] for the top
+ * level.
+ * @param useUnmergedTree Whether to include merged children
+ * @return A list of integer IDs representing the child views of the component.
+ */
+ List<Integer> semanticallyRelevantChildComponents(Component component, boolean useUnmergedTree);
+
+ /**
+ * Retrieves the semantic modifiers associated with a given component.
+ *
+ * @param component The component for which to retrieve semantic modifiers.
+ * @return A list of semantic modifiers applicable to the component.
+ */
+ List<AccessibilitySemantics> semanticModifiersForComponent(Component component);
+
+ /**
+ * Gets all applied merge modes of the given component. A Merge mode is one of Set, Merge or
+ * Clear and describes how to apply and combine hierarchical semantics.
+ *
+ * @param component The component to merge the mode for.
+ * @return The effective merge modes, potentially conflicting but resolved to a single value.
+ */
+ CoreSemantics.Mode mergeMode(Component component);
+
+ /**
+ * Finds a component by its ID.
+ *
+ * @param id the ID of the component to find
+ * @return the component with the given ID, or {@code null} if no such component exists
+ */
+ @Nullable
+ Component findComponentById(int id);
+
+ @Nullable
+ Integer getComponentIdAt(PointF point);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
new file mode 100644
index 000000000000..13641025a33b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+/**
+ * This class is the entry point for finding the AccessibilityDelegate for a RemoteCompose document.
+ */
+public class RemoteComposeTouchHelper {
+ /** Get the platform specific accessibility delegate registrar */
+ public static final RemoteComposeAccessibilityRegistrar REGISTRAR =
+ new PlatformRemoteComposeAccessibilityRegistrar();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
new file mode 100644
index 000000000000..832b5426f476
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+
+import java.util.List;
+
+/**
+ * An interface for applying semantic information to a semantics node.
+ *
+ * <p>Implementations of this interface are responsible for taking a node represented by [nodeInfo]
+ * and applying a list of [semantics] (representing accessible actions and properties) to it. This
+ * process might involve: - Modifying the node's properties (e.g., content description, clickable
+ * state). - Adding a child node to represent a specific semantic element. - Performing any other
+ * action necessary to make the node semantically meaningful and accessible to assistive
+ * technologies.
+ *
+ * @param <N> The type representing information about the node. This could be an Androidx
+ * `AccessibilityNodeInfoCompat`, or potentially a platform `AccessibilityNodeInfo`.
+ */
+public interface SemanticNodeApplier<N> {
+ void applyComponent(
+ RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+ N nodeInfo,
+ Component component,
+ List<AccessibilitySemantics> semantics);
+
+ String VIRTUAL_VIEW_ID_KEY = "VirtualViewId";
+}
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 370289a84502..1e9ba78e52c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -18,17 +18,19 @@ package com.android.internal.widget.remotecompose.core;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
+import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.Theme;
import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
-import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
@@ -38,12 +40,14 @@ import com.android.internal.widget.remotecompose.core.operations.layout.TouchDow
import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -53,13 +57,14 @@ import java.util.Set;
public class CoreDocument {
private static final boolean DEBUG = false;
+ private static final int DOCUMENT_API_LEVEL = 2;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@Nullable RootLayoutComponent mRootLayoutComponent = null;
@NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState();
- @NonNull TimeVariables mTimeVariables = new TimeVariables();
+ @VisibleForTesting @NonNull public TimeVariables mTimeVariables = new TimeVariables();
// Semantic version of the document
@NonNull Version mVersion = new Version(0, 1, 0);
@@ -86,6 +91,11 @@ public class CoreDocument {
private int mLastId = 1; // last component id when inflating the file
+ /** Returns a version number that is monotonically increasing. */
+ public static int getDocumentApiLevel() {
+ return DOCUMENT_API_LEVEL;
+ }
+
@Nullable
public String getContentDescription() {
return mContentDescription;
@@ -434,11 +444,11 @@ public class CoreDocument {
mActionListeners.clear();
}
- public interface ClickCallbacks {
- void click(int id, @Nullable String metadata);
+ public interface IdActionCallback {
+ void onAction(int id, @Nullable String metadata);
}
- @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+ @NonNull HashSet<IdActionCallback> mIdActionListeners = new HashSet<>();
@NonNull HashSet<TouchListener> mTouchListeners = new HashSet<>();
@NonNull HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
@@ -463,6 +473,21 @@ public class CoreDocument {
float mBottom;
@Nullable final String mMetadata;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClickAreaRepresentation)) return false;
+ ClickAreaRepresentation that = (ClickAreaRepresentation) o;
+ return mId == that.mId
+ && Objects.equals(mContentDescription, that.mContentDescription)
+ && Objects.equals(mMetadata, that.mMetadata);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mContentDescription, mMetadata);
+ }
+
public ClickAreaRepresentation(
int id,
@Nullable String contentDescription,
@@ -565,6 +590,7 @@ public class CoreDocument {
TouchUpModifierOperation currentTouchUpModifier = null;
TouchCancelModifierOperation currentTouchCancelModifier = null;
LoopOperation currentLoop = null;
+ ScrollModifierOperation currentScrollModifier = null;
mLastId = -1;
for (Operation o : operations) {
@@ -579,8 +605,8 @@ public class CoreDocument {
mLastId = component.getComponentId();
}
} else if (o instanceof ComponentEnd) {
- if (currentComponent instanceof LayoutComponent) {
- ((LayoutComponent) currentComponent).inflate();
+ if (currentComponent != null) {
+ currentComponent.inflate();
}
components.remove(components.size() - 1);
if (!components.isEmpty()) {
@@ -602,6 +628,9 @@ public class CoreDocument {
} else if (o instanceof TouchCancelModifierOperation) {
currentTouchCancelModifier = (TouchCancelModifierOperation) o;
ops = currentTouchCancelModifier.getList();
+ } else if (o instanceof ScrollModifierOperation) {
+ currentScrollModifier = (ScrollModifierOperation) o;
+ ops = currentScrollModifier.getList();
} else if (o instanceof OperationsListEnd) {
ops = currentComponent.getList();
if (currentClickModifier != null) {
@@ -616,6 +645,9 @@ public class CoreDocument {
} else if (currentTouchCancelModifier != null) {
ops.add(currentTouchCancelModifier);
currentTouchCancelModifier = null;
+ } else if (currentScrollModifier != null) {
+ ops.add(currentScrollModifier);
+ currentScrollModifier = null;
}
} else if (o instanceof LoopOperation) {
currentLoop = (LoopOperation) o;
@@ -665,6 +697,7 @@ public class CoreDocument {
}
}
}
+ op.markNotDirty();
op.apply(context);
}
}
@@ -740,9 +773,13 @@ public class CoreDocument {
float right,
float bottom,
@Nullable String metadata) {
- mClickAreas.add(
+
+ ClickAreaRepresentation car =
new ClickAreaRepresentation(
- id, contentDescription, left, top, right, bottom, metadata));
+ id, contentDescription, left, top, right, bottom, metadata);
+
+ boolean old = mClickAreas.remove(car);
+ mClickAreas.add(car);
}
/**
@@ -755,12 +792,12 @@ public class CoreDocument {
}
/**
- * Add a click listener. This will get called when a click is detected on the document
+ * Add an id action listener. This will get called when e.g. a click is detected on the document
*
- * @param callback called when a click area has been hit, passing the click are id and metadata.
+ * @param callback called when an action is executed, passing the id and metadata.
*/
- public void addClickListener(@NonNull ClickCallbacks callback) {
- mClickListeners.add(callback);
+ public void addIdActionListener(@NonNull IdActionCallback callback) {
+ mIdActionListeners.add(callback);
}
/**
@@ -769,8 +806,8 @@ public class CoreDocument {
* @return set of click listeners
*/
@NonNull
- public HashSet<CoreDocument.ClickCallbacks> getClickListeners() {
- return mClickListeners;
+ public HashSet<IdActionCallback> getIdActionListeners() {
+ return mIdActionListeners;
}
/**
@@ -799,15 +836,15 @@ public class CoreDocument {
warnClickListeners(clickArea);
}
}
- for (ClickCallbacks listener : mClickListeners) {
- listener.click(id, "");
+ for (IdActionCallback listener : mIdActionListeners) {
+ listener.onAction(id, "");
}
}
/** Warn click listeners when a click area is activated */
private void warnClickListeners(@NonNull ClickAreaRepresentation clickArea) {
- for (ClickCallbacks listener : mClickListeners) {
- listener.click(clickArea.mId, clickArea.mMetadata);
+ for (IdActionCallback listener : mIdActionListeners) {
+ listener.onAction(clickArea.mId, clickArea.mMetadata);
}
}
@@ -881,7 +918,7 @@ public class CoreDocument {
}
if (mRootLayoutComponent != null) {
for (Component component : mAppliedTouchOperations) {
- component.onTouchUp(context, this, x, y, true);
+ component.onTouchUp(context, this, x, y, dx, dy, true);
}
mAppliedTouchOperations.clear();
}
@@ -1039,7 +1076,13 @@ public class CoreDocument {
|| context.getTheme() == Theme.UNSPECIFIED;
}
if (apply) {
- op.apply(context);
+ if (op.isDirty() || op instanceof PaintOperation) {
+ if (op.isDirty() && op instanceof VariableSupport) {
+ op.markNotDirty();
+ ((VariableSupport) op).updateVariables(context);
+ }
+ op.apply(context);
+ }
}
}
if (context.getPaintContext().doesNeedsRepaint()
@@ -1047,7 +1090,6 @@ public class CoreDocument {
mRepaintNext = 1;
}
context.mMode = RemoteContext.ContextMode.UNSET;
- // System.out.println(">> " + ( System.nanoTime() - time)*1E-6f+" ms");
if (DEBUG && mRootLayoutComponent != null) {
System.out.println(mRootLayoutComponent.displayHierarchy());
}
@@ -1143,4 +1185,30 @@ public class CoreDocument {
public List<Operation> getOperations() {
return mOperations;
}
+
+ /** defines if a shader can be run */
+ public interface ShaderControl {
+ boolean isShaderValid(String shader);
+ }
+
+ /**
+ * validate the shaders
+ *
+ * @param context the remote context
+ * @param ctl the call back to allow evaluation of shaders
+ */
+ public void checkShaders(RemoteContext context, ShaderControl ctl) {
+ int count = 0;
+ for (Operation op : mOperations) {
+ if (op instanceof TextData) {
+ op.apply(context);
+ }
+ if (op instanceof ShaderData) {
+ ShaderData sd = (ShaderData) op;
+ int id = sd.getShaderTextId();
+ String str = context.getText(id);
+ sd.enable(ctl.isShaderValid(str));
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index 6f6a0a892964..150ebd0d3dfc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
/** Base interface for RemoteCompose operations */
public abstract class Operation {
+ private static final boolean ENABLE_DIRTY_FLAG_OPTIMIZATION = true;
+
/** add the operation to the buffer */
public abstract void write(@NonNull WireBuffer buffer);
@@ -33,4 +35,30 @@ public abstract class Operation {
/** Debug utility to display an operation + indentation */
@NonNull
public abstract String deepToString(@NonNull String indent);
+
+ private boolean mDirty = true;
+
+ /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+ public void markDirty() {
+ mDirty = true;
+ }
+
+ /** Mark the operation as "not dirty" */
+ public void markNotDirty() {
+ if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+ mDirty = false;
+ }
+ }
+
+ /**
+ * Returns true if the operation is marked as "dirty"
+ *
+ * @return true if dirty
+ */
+ public boolean isDirty() {
+ if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+ return mDirty;
+ }
+ return true;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
index 741303a40bc9..06beffcac0de 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
@@ -33,4 +33,14 @@ public interface OperationInterface {
/** Debug utility to display an operation + indentation */
@NonNull
String deepToString(@NonNull String indent);
+
+ /**
+ * Returns true if the operation is marked as "dirty"
+ *
+ * @return true if dirty
+ */
+ boolean isDirty();
+
+ /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+ void markNotDirty();
}
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 006fe3afb4d8..04e490fa5214 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -98,8 +98,10 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation;
@@ -110,6 +112,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
import com.android.internal.widget.remotecompose.core.types.LongConstant;
@@ -126,6 +129,9 @@ public class Operations {
public static final int CLICK_AREA = 64;
public static final int ROOT_CONTENT_BEHAVIOR = 65;
public static final int ROOT_CONTENT_DESCRIPTION = 103;
+ // TODO reorder before submitting
+ public static final int ACCESSIBILITY_SEMANTICS = 250;
+ // public static final int ACCESSIBILITY_CUSTOM_ACTION = 251;
////////////////////////////////////////
// Draw commands
@@ -223,6 +229,8 @@ public class Operations {
public static final int MODIFIER_ZINDEX = 223;
public static final int MODIFIER_GRAPHICS_LAYER = 224;
public static final int MODIFIER_SCROLL = 226;
+ public static final int MODIFIER_MARQUEE = 228;
+ public static final int MODIFIER_RIPPLE = 229;
public static final int LOOP_START = 215;
public static final int LOOP_END = 216;
@@ -327,6 +335,8 @@ public class Operations {
map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
map.put(MODIFIER_SCROLL, ScrollModifierOperation::read);
+ map.put(MODIFIER_MARQUEE, MarqueeModifierOperation::read);
+ map.put(MODIFIER_RIPPLE, RippleModifierOperation::read);
map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
@@ -362,5 +372,8 @@ public class Operations {
map.put(PATH_TWEEN, PathTween::read);
map.put(PATH_CREATE, PathCreate::read);
map.put(PATH_ADD, PathAppend::read);
+
+ map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read);
+ // map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
}
}
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 3a5d68db8a37..0ae7a94b948d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,7 +15,6 @@
*/
package com.android.internal.widget.remotecompose.core;
-import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.ADD;
import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.MUL;
import android.annotation.NonNull;
@@ -81,6 +80,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Componen
import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
@@ -92,8 +92,10 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
@@ -1610,7 +1612,7 @@ public class RemoteComposeBuffer {
* create and animation based on description and return as an array of floats. see
* addAnimatedFloat
*
- * @param duration the duration of the aimation
+ * @param duration the duration of the animation in seconds
* @param type the type of animation
* @param spec the parameters of the animation if any
* @param initialValue the initial value if it animates to a start
@@ -1699,6 +1701,9 @@ public class RemoteComposeBuffer {
float notchMax = this.reserveFloatVariable();
float touchExpressionDirection =
direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+
+ ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+
this.addTouchExpression(
positionId,
0f,
@@ -1707,20 +1712,13 @@ public class RemoteComposeBuffer {
0f,
3,
new float[] {
- touchExpressionDirection,
- -1,
- // TODO: remove this CONTINUOUS_SEC hack...
- MUL,
- RemoteContext.FLOAT_CONTINUOUS_SEC,
- 0f,
- MUL,
- ADD
+ touchExpressionDirection, -1, MUL,
},
TouchExpression.STOP_NOTCHES_EVEN,
new float[] {notches, notchMax},
null);
- ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+ OperationsListEnd.apply(mBuffer);
}
/**
@@ -1786,6 +1784,38 @@ public class RemoteComposeBuffer {
ZIndexModifierOperation.apply(mBuffer, value);
}
+ /** Add a ripple effect on touch down as a modifier */
+ public void addModifierRipple() {
+ RippleModifierOperation.apply(mBuffer);
+ }
+
+ /**
+ * Add a marquee modifier
+ *
+ * @param iterations
+ * @param animationMode
+ * @param repeatDelayMillis
+ * @param initialDelayMillis
+ * @param spacing
+ * @param velocity
+ */
+ public void addModifierMarquee(
+ int iterations,
+ int animationMode,
+ float repeatDelayMillis,
+ float initialDelayMillis,
+ float spacing,
+ float velocity) {
+ MarqueeModifierOperation.apply(
+ mBuffer,
+ iterations,
+ animationMode,
+ repeatDelayMillis,
+ initialDelayMillis,
+ spacing,
+ velocity);
+ }
+
/**
* Add a graphics layer
*
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 f5f9e214723b..5c3df7e95a1f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -48,6 +48,10 @@ public class RemoteComposeState implements CollectionsAccess {
private final IntMap<DataMap> mDataMapMap = new IntMap<>();
private final IntMap<Object> mObjectMap = new IntMap<>();
+ // path information
+ private final IntMap<Object> mPathMap = new IntMap<>();
+ private final IntMap<float[]> mPathData = new IntMap<>();
+
private final boolean[] mColorOverride = new boolean[MAX_COLORS];
@NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
@@ -131,12 +135,44 @@ public class RemoteComposeState implements CollectionsAccess {
}
}
- private final IntMap<float[]> mPathData = new IntMap<>();
+ /**
+ * Get the path asociated with the Data
+ *
+ * @param id
+ * @return
+ */
+ public Object getPath(int id) {
+ return mPathMap.get(id);
+ }
+
+ /**
+ * Cache a path object. Object will be cleared if you update path data.
+ *
+ * @param id number asociated with path
+ * @param path the path object typically Android Path
+ */
+ public void putPath(int id, Object path) {
+ mPathMap.put(id, path);
+ }
+ /**
+ * The path data the Array of floats that is asoicated with the path It also removes the current
+ * path object.
+ *
+ * @param id the integer asociated with the data and path
+ * @param data the array of floats that represents the path
+ */
public void putPathData(int id, float[] data) {
mPathData.put(id, data);
+ mPathMap.remove(id);
}
+ /**
+ * Get the path data asociated with the id
+ *
+ * @param id number that represents the path
+ * @return path data
+ */
public float[] getPathData(int id) {
return mPathData.get(id);
}
@@ -283,7 +319,7 @@ public class RemoteComposeState implements CollectionsAccess {
ArrayList<VariableSupport> v = mVarListeners.get(id);
if (v != null && mRemoteContext != null) {
for (VariableSupport c : v) {
- c.updateVariables(mRemoteContext);
+ c.markDirty();
}
}
}
@@ -297,6 +333,7 @@ public class RemoteComposeState implements CollectionsAccess {
public void overrideColor(int id, int color) {
mColorOverride[id] = true;
mColorMap.put(id, color);
+ updateListeners(id);
}
/** Clear the color Overrides */
@@ -426,9 +463,6 @@ public class RemoteComposeState implements CollectionsAccess {
* @return
*/
public int getOpsToUpdate(@NonNull RemoteContext context) {
- for (VariableSupport vs : mAllVarListeners) {
- vs.updateVariables(context);
- }
if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
return 1;
}
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 6eb8463ab308..c03f44bfc162 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -214,6 +214,13 @@ public abstract class RemoteContext {
*/
public abstract void hapticEffect(int type);
+ /** Set the repaint flag. This will trigger a repaint of the current document. */
+ public void needsRepaint() {
+ if (mPaintContext != null) {
+ mPaintContext.needsRepaint();
+ }
+ }
+
/**
* The context can be used in a few different mode, allowing operations to skip being executed:
* - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg
@@ -486,6 +493,9 @@ public abstract class RemoteContext {
public static final int ID_DENSITY = 27;
+ /** Defines when the last build was made */
+ public static final int ID_API_LEVEL = 28;
+
public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY);
/** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
@@ -559,6 +569,9 @@ public abstract class RemoteContext {
/** Ambient light level in SI lux */
public static final float FLOAT_LIGHT = Utils.asNan(ID_LIGHT);
+ /** When was this player built */
+ public static final float FLOAT_API_LEVEL = Utils.asNan(ID_API_LEVEL);
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index 14aed2f0c173..cd7ebec67a46 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -24,14 +24,14 @@ import java.time.ZoneOffset;
/** This generates the standard system variables for time. */
public class TimeVariables {
+ private static final float BUILD = 0.01f;
+
/**
* This class populates all time variables in the system
*
* @param context
*/
- public void updateTime(@NonNull RemoteContext context) {
- LocalDateTime dateTime =
- LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
+ public void updateTime(@NonNull RemoteContext context, ZoneId zoneId, LocalDateTime dateTime) {
// This define the time in the format
// seconds run from Midnight=0 quantized to seconds hour 0..3599
// minutes run from Midnight=0 quantized to minutes 0..1439
@@ -48,8 +48,7 @@ public class TimeVariables {
float sec = currentSeconds + dateTime.getNano() * 1E-9f;
int day_week = dateTime.getDayOfWeek().getValue();
- ZoneId zone = ZoneId.systemDefault();
- OffsetDateTime offsetDateTime = dateTime.atZone(zone).toOffsetDateTime();
+ OffsetDateTime offsetDateTime = dateTime.atZone(zoneId).toOffsetDateTime();
ZoneOffset offset = offsetDateTime.getOffset();
context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC, offset.getTotalSeconds());
@@ -60,5 +59,18 @@ public class TimeVariables {
context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month);
context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, month);
context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week);
+ context.loadFloat(RemoteContext.ID_API_LEVEL, CoreDocument.getDocumentApiLevel() + BUILD);
+ }
+
+ /**
+ * This class populates all time variables in the system
+ *
+ * @param context
+ */
+ public void updateTime(@NonNull RemoteContext context) {
+ ZoneId zone = ZoneId.systemDefault();
+ LocalDateTime dateTime = LocalDateTime.now(zone);
+
+ updateTime(context, zone, dateTime);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
index 3dda678c2a3a..611ba97c10cb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
@@ -15,10 +15,34 @@
*/
package com.android.internal.widget.remotecompose.core;
+/** Interface used by objects to register for touch events */
public interface TouchListener {
+ /**
+ * Called when touch down happens
+ *
+ * @param context The players context
+ * @param x the x location of the down touch
+ * @param y the y location of the down touch
+ */
void touchDown(RemoteContext context, float x, float y);
+ /**
+ * called on touch up
+ *
+ * @param context the players context
+ * @param x the x location
+ * @param y the y location
+ * @param dx the x velocity when the touch up happened
+ * @param dy the y valocity when the touch up happened
+ */
void touchUp(RemoteContext context, float x, float y, float dx, float dy);
+ /**
+ * Drag event (occur between down and up)
+ *
+ * @param context the players context
+ * @param x the x coord of the drag
+ * @param y the y coord of the drag
+ */
void touchDrag(RemoteContext context, float x, float y);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
index e9fa8976637e..1f3e290a0f04 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
@@ -36,4 +36,7 @@ public interface VariableSupport {
* @param context
*/
void updateVariables(@NonNull RemoteContext context);
+
+ /** Mark the operation as dirty to indicate that the variables it references are out of date. */
+ void markDirty();
}
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 27ba6528a703..784897b04991 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
@@ -45,15 +45,39 @@ public class BitmapData extends Operation implements SerializableToString {
short mType;
short mEncoding;
@NonNull final byte[] mBitmap;
+
+ /** The max size of width or height */
public static final int MAX_IMAGE_DIMENSION = 8000;
+
+ /** The data is encoded in the file (default) */
public static final short ENCODING_INLINE = 0;
+
+ /** The data is encoded in the url */
public static final short ENCODING_URL = 1;
+
+ /** The data is encoded as a reference to file */
public static final short ENCODING_FILE = 2;
+
+ /** The data is encoded as PNG_8888 (default) */
public static final short TYPE_PNG_8888 = 0;
+
+ /** The data is encoded as PNG */
public static final short TYPE_PNG = 1;
+
+ /** The data is encoded as RAW 8 bit */
public static final short TYPE_RAW8 = 2;
+
+ /** The data is encoded as RAW 8888 bit */
public static final short TYPE_RAW8888 = 3;
+ /**
+ * create a bitmap structure
+ *
+ * @param imageId the id to store the image
+ * @param width the width of the image
+ * @param height the height of the image
+ * @param bitmap the data
+ */
public BitmapData(int imageId, int width, int height, @NonNull byte[] bitmap) {
this.mImageId = imageId;
this.mImageWidth = width;
@@ -61,10 +85,20 @@ public class BitmapData extends Operation implements SerializableToString {
this.mBitmap = bitmap;
}
+ /**
+ * The width of the image
+ *
+ * @return the width
+ */
public int getWidth() {
return mImageWidth;
}
+ /**
+ * The height of the image
+ *
+ * @return the height
+ */
public int getHeight() {
return mImageHeight;
}
@@ -80,6 +114,11 @@ public class BitmapData extends Operation implements SerializableToString {
return "BITMAP DATA " + mImageId;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -94,6 +133,15 @@ public class BitmapData extends Operation implements SerializableToString {
return OP_CODE;
}
+ /**
+ * Add the image to the document
+ *
+ * @param buffer document to write to
+ * @param imageId the id the image will be stored under
+ * @param width the width of the image
+ * @param height the height of the image
+ * @param bitmap the data used to store/encode the image
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
@@ -107,6 +155,17 @@ public class BitmapData extends Operation implements SerializableToString {
buffer.writeBuffer(bitmap);
}
+ /**
+ * Add the image to the document (using the ehanced encoding)
+ *
+ * @param buffer document to write to
+ * @param imageId the id the image will be stored under
+ * @param type the type of image
+ * @param width the width of the image
+ * @param encoding the encoding
+ * @param height the height of the image
+ * @param bitmap the data used to store/encode the image
+ */
public static void apply(
@NonNull WireBuffer buffer,
int imageId,
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 5e5e5653053f..bb112d1cb732 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
@@ -21,14 +21,17 @@ import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
import java.util.List;
/** Add a click area to the document */
-public class ClickArea extends Operation implements RemoteComposeOperation {
+public class ClickArea extends Operation
+ implements RemoteComposeOperation, AccessibleComponent, VariableSupport {
private static final int OP_CODE = Operations.CLICK_AREA;
private static final String CLASS_NAME = "ClickArea";
int mId;
@@ -37,6 +40,10 @@ public class ClickArea extends Operation implements RemoteComposeOperation {
float mTop;
float mRight;
float mBottom;
+ float mOutLeft;
+ float mOutTop;
+ float mOutRight;
+ float mOutBottom;
int mMetadata;
/**
@@ -61,11 +68,35 @@ public class ClickArea extends Operation implements RemoteComposeOperation {
int metadata) {
this.mId = id;
this.mContentDescription = contentDescription;
- this.mLeft = left;
- this.mTop = top;
- this.mRight = right;
- this.mBottom = bottom;
- this.mMetadata = metadata;
+ mOutLeft = mLeft = left;
+ mOutTop = mTop = top;
+ mOutRight = mRight = right;
+ mOutBottom = mBottom = bottom;
+ mMetadata = metadata;
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ if (Float.isNaN(mLeft)) {
+ context.listensTo(Utils.idFromNan(mLeft), this);
+ }
+ if (Float.isNaN(mTop)) {
+ context.listensTo(Utils.idFromNan(mTop), this);
+ }
+ if (Float.isNaN(mRight)) {
+ context.listensTo(Utils.idFromNan(mRight), this);
+ }
+ if (Float.isNaN(mBottom)) {
+ context.listensTo(Utils.idFromNan(mBottom), this);
+ }
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ mOutLeft = Float.isNaN(mLeft) ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft;
+ mOutTop = Float.isNaN(mTop) ? context.getFloat(Utils.idFromNan(mTop)) : mTop;
+ mRight = Float.isNaN(mRight) ? context.getFloat(Utils.idFromNan(mRight)) : mRight;
+ mOutBottom = Float.isNaN(mBottom) ? context.getFloat(Utils.idFromNan(mBottom)) : mBottom;
}
@Override
@@ -101,10 +132,8 @@ public class ClickArea extends Operation implements RemoteComposeOperation {
@Override
public void apply(@NonNull RemoteContext context) {
- if (context.getMode() != RemoteContext.ContextMode.DATA) {
- return;
- }
- context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+ context.addClickArea(
+ mId, mContentDescription, mOutLeft, mOutTop, mOutRight, mOutBottom, mMetadata);
}
@NonNull
@@ -113,6 +142,11 @@ public class ClickArea extends Operation implements RemoteComposeOperation {
return indent + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -127,6 +161,21 @@ public class ClickArea extends Operation implements RemoteComposeOperation {
return OP_CODE;
}
+ @Override
+ public Integer getContentDescriptionId() {
+ return mContentDescription;
+ }
+
+ /**
+ * @param buffer
+ * @param id
+ * @param contentDescription
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ * @param metadata
+ */
public static void apply(
@NonNull WireBuffer buffer,
int id,
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 2fe56d3ec935..b55f25c911fe 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
@@ -83,6 +83,11 @@ public class ClipPath extends PaintOperation {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 defa656b44e6..5a495d54e8da 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
@@ -28,8 +28,8 @@ import java.util.List;
/** Support clip with a rectangle */
public class ClipRect extends DrawBase4 {
- public static final int OP_CODE = Operations.CLIP_RECT;
- public static final String CLASS_NAME = "ClipRect";
+ private static final int OP_CODE = Operations.CLIP_RECT;
+ private static final String CLASS_NAME = "ClipRect";
/**
* Read this operation and add it to the list of operations
@@ -51,6 +51,11 @@ public class ClipRect extends DrawBase4 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 d86576dd99f2..68020157b8d1 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
@@ -61,6 +61,11 @@ public class ColorConstant extends Operation {
return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 66f128f8f478..b385ecd9e5f7 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
@@ -199,6 +199,11 @@ public class ColorExpression extends Operation implements VariableSupport {
+ ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 19c219bc0121..3e852364cfee 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
@@ -31,8 +31,8 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin
import java.util.List;
public class ComponentValue extends Operation implements SerializableToString {
- public static final int OP_CODE = Operations.COMPONENT_VALUE;
- public static final String CLASS_NAME = "ComponentValue";
+ private static final int OP_CODE = Operations.COMPONENT_VALUE;
+ private static final String CLASS_NAME = "ComponentValue";
public static final int WIDTH = 0;
public static final int HEIGHT = 1;
@@ -50,6 +50,11 @@ public class ComponentValue extends Operation implements SerializableToString {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index e888074cda74..ff85721027f7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -129,6 +129,11 @@ public class DataMapIds extends Operation {
operations.add(data);
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Data Operations", OP_CODE, CLASS_NAME)
.description("Encode a collection of name id pairs")
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 3f95f02747f9..fd1f41065dd9 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
@@ -26,8 +26,9 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import java.util.List;
+/** Draw an Arc command the specified arc, will be scaled to fit inside the specified oval. */
public class DrawArc extends DrawBase6 {
- public static final int OP_CODE = Operations.DRAW_ARC;
+ private static final int OP_CODE = Operations.DRAW_ARC;
private static final String CLASS_NAME = "DrawArc";
/**
@@ -114,8 +115,20 @@ public class DrawArc extends DrawBase6 {
"Sweep angle (in degrees) measured clockwise");
}
- public DrawArc(float v1, float v2, float v3, float v4, float v5, float v6) {
- super(v1, v2, v3, v4, v5, v6);
+ /**
+ * Create Draw Arc command Draw the specified arc, which will be scaled to fit inside the
+ * specified oval.
+ *
+ * @param left the left side of the oval
+ * @param top the top of the oval
+ * @param right the right side of the oval
+ * @param bottom the bottom of the oval
+ * @param startAngle Starting angle (in degrees) where the arc begins
+ * @param sweepAngle Sweep angle (in degrees) measured clockwise
+ */
+ public DrawArc(
+ float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
+ super(left, top, right, bottom, startAngle, sweepAngle);
mName = "DrawArc";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index 6c288a35621d..64c2730e5f9a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -145,6 +145,11 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor
return null;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "DrawBase6";
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 69f5cc52a78a..cdb527dee460 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
@@ -118,6 +118,11 @@ public class DrawBitmap extends PaintOperation implements VariableSupport {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 66646d7c2faa..638fe148d746 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
@@ -24,11 +24,12 @@ 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.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
import java.util.List;
/** Operation to draw a given cached bitmap */
-public class DrawBitmapInt extends PaintOperation {
+public class DrawBitmapInt extends PaintOperation implements AccessibleComponent {
private static final int OP_CODE = Operations.DRAW_BITMAP_INT;
private static final String CLASS_NAME = "DrawBitmapInt";
int mImageId;
@@ -106,6 +107,16 @@ public class DrawBitmapInt extends PaintOperation {
+ ";";
}
+ @Override
+ public Integer getContentDescriptionId() {
+ return mContentDescId;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -170,6 +181,11 @@ public class DrawBitmapInt extends PaintOperation {
operations.add(op);
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
.description("Draw a bitmap using integer coordinates")
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 170148608ab3..d6467c926747 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
@@ -27,11 +27,13 @@ import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
import java.util.List;
/** Operation to draw a given cached bitmap */
-public class DrawBitmapScaled extends PaintOperation implements VariableSupport {
+public class DrawBitmapScaled extends PaintOperation
+ implements VariableSupport, AccessibleComponent {
private static final int OP_CODE = Operations.DRAW_BITMAP_SCALED;
private static final String CLASS_NAME = "DrawBitmapScaled";
int mImageId;
@@ -191,6 +193,16 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport
+ Utils.floatToString(mScaleFactor, mOutScaleFactor);
}
+ @Override
+ public Integer getContentDescriptionId() {
+ return mContentDescId;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 e6aecdbf8bbe..735e262ce94f 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
@@ -50,6 +50,11 @@ public class DrawCircle extends DrawBase3 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 04f32642d5fa..f3a190d98960 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
@@ -52,6 +52,11 @@ public class DrawLine extends DrawBase4 implements SerializableToString {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 0a50042b11c7..a009874302e0 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
@@ -50,6 +50,11 @@ public class DrawOval extends DrawBase4 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 41b8243f070f..398cf4892e12 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
@@ -62,6 +62,11 @@ public class DrawPath extends PaintOperation {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 7e22550d6544..38477ad0deb6 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
@@ -51,6 +51,11 @@ public class DrawRect extends DrawBase4 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 7616df09b6cc..51ece77a872a 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
@@ -27,7 +27,7 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import java.util.List;
public class DrawSector extends DrawBase6 {
- public static final int OP_CODE = Operations.DRAW_SECTOR;
+ private static final int OP_CODE = Operations.DRAW_SECTOR;
private static final String CLASS_NAME = "DrawSector";
/**
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 2c5d790b2f2a..8adba1d2616a 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
@@ -121,6 +121,11 @@ public class DrawText extends PaintOperation implements VariableSupport {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 7de52b8e5f3e..f839922b25e2 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
@@ -130,6 +130,11 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 18d9fdf1b671..86f3c992f2fb 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
@@ -98,6 +98,11 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "DrawTextOnPath";
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 b83e4c2191b2..d4d4a5ecf6b9 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
@@ -108,6 +108,11 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "DrawTweenPath";
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 7dd435a5c5b1..e04e691c312c 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
@@ -51,6 +51,11 @@ public class FloatConstant extends Operation {
return "FloatConstant[" + mTextId + "] = " + mValue;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 3d92e129a236..c1872fd0fed0 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
@@ -139,6 +139,11 @@ public class FloatExpression extends Operation implements VariableSupport {
}
}
+ // Keep track of the last computed value when we are animated,
+ // e.g. if FloatAnimation or Spring is used, so that we can
+ // ask for a repaint.
+ float mLastAnimatedValue = Float.NaN;
+
@Override
public void apply(@NonNull RemoteContext context) {
updateVariables(context);
@@ -146,12 +151,23 @@ public class FloatExpression extends Operation implements VariableSupport {
if (Float.isNaN(mLastChange)) {
mLastChange = t;
}
+ float lastComputedValue;
if (mFloatAnimation != null && !Float.isNaN(mLastCalculatedValue)) {
float f = mFloatAnimation.get(t - mLastChange);
context.loadFloat(mId, f);
+ lastComputedValue = f;
+ if (lastComputedValue != mLastAnimatedValue) {
+ mLastAnimatedValue = lastComputedValue;
+ context.needsRepaint();
+ }
} else if (mSpring != null) {
float f = mSpring.get(t - mLastChange);
context.loadFloat(mId, f);
+ lastComputedValue = f;
+ if (lastComputedValue != mLastAnimatedValue) {
+ mLastAnimatedValue = lastComputedValue;
+ context.needsRepaint();
+ }
} else {
float v =
mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
@@ -205,6 +221,11 @@ public class FloatExpression extends Operation implements VariableSupport {
+ ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -236,6 +257,9 @@ public class FloatExpression extends Operation implements VariableSupport {
buffer.writeInt(id);
int len = value.length;
+ if (len > MAX_EXPRESSION_SIZE) {
+ throw new RuntimeException(AnimatedFloatExpression.toString(value, null) + " to long");
+ }
if (animation != null) {
len |= (animation.length << 16);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 04e4346cf05d..656dc09c396f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -39,7 +39,7 @@ public class Header extends Operation implements RemoteComposeOperation {
private static final int OP_CODE = Operations.HEADER;
private static final String CLASS_NAME = "Header";
public static final int MAJOR_VERSION = 0;
- public static final int MINOR_VERSION = 1;
+ public static final int MINOR_VERSION = 2;
public static final int PATCH_VERSION = 0;
int mMajorVersion;
@@ -115,6 +115,11 @@ public class Header extends Operation implements RemoteComposeOperation {
return toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 67274af7c283..f04f30dc62fb 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
@@ -136,6 +136,11 @@ public class IntegerExpression extends Operation implements VariableSupport {
return "IntegerExpression[" + mId + "] = (" + s + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 aed597ae7494..044430d1e3c1 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
@@ -26,6 +26,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import java.util.List;
+/** The restore previous matrix command */
public class MatrixRestore extends PaintOperation {
private static final int OP_CODE = Operations.MATRIX_RESTORE;
private static final String CLASS_NAME = "MatrixRestore";
@@ -54,6 +55,11 @@ public class MatrixRestore extends PaintOperation {
return "MatrixRestore";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 fece143ebfa1..57f5a0ebfab1 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
@@ -26,8 +26,9 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import java.util.List;
+/** The rotate the rendering command */
public class MatrixRotate extends DrawBase3 {
- public static final int OP_CODE = Operations.MATRIX_ROTATE;
+ private static final int OP_CODE = Operations.MATRIX_ROTATE;
private static final String CLASS_NAME = "MatrixRotate";
/**
@@ -57,6 +58,11 @@ public class MatrixRotate extends DrawBase3 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 7eb7b3ffaf34..aec316aea361 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
@@ -26,6 +26,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import java.util.List;
+/** The save the matrix state command */
public class MatrixSave extends PaintOperation {
private static final int OP_CODE = Operations.MATRIX_SAVE;
private static final String CLASS_NAME = "MatrixSave";
@@ -52,6 +53,11 @@ public class MatrixSave extends PaintOperation {
operations.add(op);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 49bdd1b06eed..07f965f7d72a 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
@@ -26,9 +26,10 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import java.util.List;
+/** Scale the rendering matrix command */
public class MatrixScale extends DrawBase4 {
- public static final int OP_CODE = Operations.MATRIX_SCALE;
- public static final String CLASS_NAME = "MatrixScale";
+ private static final int OP_CODE = Operations.MATRIX_SCALE;
+ private static final String CLASS_NAME = "MatrixScale";
/**
* Read this operation and add it to the list of operations
@@ -50,6 +51,11 @@ public class MatrixScale extends DrawBase4 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 54b6fd1fa9ae..b31492d2cb57 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
@@ -27,9 +27,10 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import java.util.List;
+/** Skew the matrix command */
public class MatrixSkew extends DrawBase2 {
- public static final int OP_CODE = Operations.MATRIX_SKEW;
- public static final String CLASS_NAME = "MatrixSkew";
+ private static final int OP_CODE = Operations.MATRIX_SKEW;
+ private static final String CLASS_NAME = "MatrixSkew";
/**
* Read this operation and add it to the list of operations
@@ -51,6 +52,11 @@ public class MatrixSkew extends DrawBase2 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 b57d83b770b2..11fa040183ce 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
@@ -26,9 +26,10 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import java.util.List;
+/** translate the matrix command */
public class MatrixTranslate extends DrawBase2 {
- public static final int OP_CODE = Operations.MATRIX_TRANSLATE;
- public static final String CLASS_NAME = "MatrixTranslate";
+ private static final int OP_CODE = Operations.MATRIX_TRANSLATE;
+ private static final String CLASS_NAME = "MatrixTranslate";
/**
* Read this operation and add it to the list of operations
@@ -50,6 +51,11 @@ public class MatrixTranslate extends DrawBase2 {
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return 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 3c82f2b27ca6..dde632e0c346 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
@@ -64,6 +64,11 @@ public class NamedVariable extends Operation {
+ mVarType;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 3c0a842371c7..daf2c5502c5d 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
@@ -61,6 +61,11 @@ public class PaintData extends PaintOperation implements VariableSupport {
return "PaintData " + "\"" + mPaintData + "\"";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -92,6 +97,11 @@ public class PaintData extends PaintOperation implements VariableSupport {
operations.add(data);
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Data Operations", OP_CODE, CLASS_NAME)
.description("Encode a Paint ")
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 2b00001a521e..7ff879e41cac 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
@@ -109,6 +109,11 @@ public class PathAppend extends PaintOperation implements VariableSupport {
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -162,11 +167,12 @@ public class PathAppend extends PaintOperation implements VariableSupport {
}
@Override
- public void paint(PaintContext context) {}
+ public void paint(PaintContext context) {
+ apply(context.getContext());
+ }
@Override
public void apply(@NonNull RemoteContext context) {
- updateVariables(context);
float[] data = context.getPathData(mInstanceId);
float[] out = mOutputPath;
if (data != null) {
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 b62f36b8db5f..75562cd8fb4c 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
@@ -48,6 +48,7 @@ public class PathCreate extends PaintOperation implements VariableSupport {
@Override
public void updateVariables(@NonNull RemoteContext context) {
+
for (int i = 0; i < mFloatPath.length; i++) {
float v = mFloatPath[i];
if (Utils.isVariable(v)) {
@@ -81,7 +82,19 @@ public class PathCreate extends PaintOperation implements VariableSupport {
@NonNull
@Override
public String toString() {
- return "PathCreate[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\"";
+ return "PathCreate["
+ + mInstanceId
+ + "] = "
+ + "\""
+ + deepToString(" ")
+ + "\"["
+ + Utils.idStringFromNan(mFloatPath[1])
+ + "] "
+ + mOutputPath[1]
+ + " ["
+ + Utils.idStringFromNan(mFloatPath[2])
+ + "] "
+ + mOutputPath[2];
}
public static final int MOVE = 10;
@@ -99,6 +112,11 @@ public class PathCreate extends PaintOperation implements VariableSupport {
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -197,7 +215,9 @@ public class PathCreate extends PaintOperation implements VariableSupport {
}
@Override
- public void paint(PaintContext context) {}
+ public void paint(PaintContext context) {
+ apply(context.getContext());
+ }
@Override
public void apply(@NonNull RemoteContext context) {
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 4ec5436c8689..85a01fc7cbc7 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
@@ -38,6 +38,7 @@ public class PathData extends Operation implements VariableSupport {
int mInstanceId;
float[] mFloatPath;
float[] mOutputPath;
+ private boolean mPathChanged = true;
PathData(int instanceId, float[] floatPath) {
mInstanceId = instanceId;
@@ -50,7 +51,11 @@ public class PathData extends Operation implements VariableSupport {
for (int i = 0; i < mFloatPath.length; i++) {
float v = mFloatPath[i];
if (Utils.isVariable(v)) {
+ float tmp = mOutputPath[i];
mOutputPath[i] = Float.isNaN(v) ? context.getFloat(Utils.idFromNan(v)) : v;
+ if (tmp != mOutputPath[i]) {
+ mPathChanged = true;
+ }
} else {
mOutputPath[i] = v;
}
@@ -107,6 +112,11 @@ public class PathData extends Operation implements VariableSupport {
public static final float CLOSE_NAN = Utils.asNan(CLOSE);
public static final float DONE_NAN = Utils.asNan(DONE);
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -216,6 +226,9 @@ public class PathData extends Operation implements VariableSupport {
@Override
public void apply(@NonNull RemoteContext context) {
- context.loadPathData(mInstanceId, mOutputPath);
+ if (mPathChanged) {
+ context.loadPathData(mInstanceId, mOutputPath);
+ }
+ mPathChanged = false;
}
}
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 a6fa680f647a..65adfeabefa6 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
@@ -80,6 +80,11 @@ public class PathTween extends PaintOperation implements VariableSupport {
+ floatToString(mTween, mTweenOut);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index aaa717629c2e..55dd88233265 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -200,6 +200,11 @@ public class RootContentBehavior extends Operation implements RemoteComposeOpera
return toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -239,6 +244,11 @@ public class RootContentBehavior extends Operation implements RemoteComposeOpera
operations.add(rootContentBehavior);
}
+ /**
+ * 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("Protocol Operations", OP_CODE, CLASS_NAME)
.description("Describes the behaviour of the root")
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 e92daa384dc3..ad86e0f2b1f3 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
@@ -24,11 +24,13 @@ 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.semantics.AccessibleComponent;
import java.util.List;
/** Describe a content description for the document */
-public class RootContentDescription extends Operation implements RemoteComposeOperation {
+public class RootContentDescription extends Operation
+ implements RemoteComposeOperation, AccessibleComponent {
private static final int OP_CODE = Operations.ROOT_CONTENT_DESCRIPTION;
private static final String CLASS_NAME = "RootContentDescription";
int mContentDescription;
@@ -43,6 +45,11 @@ public class RootContentDescription extends Operation implements RemoteComposeOp
}
@Override
+ public boolean isInterestingForSemantics() {
+ return mContentDescription != 0;
+ }
+
+ @Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mContentDescription);
}
@@ -64,6 +71,16 @@ public class RootContentDescription extends Operation implements RemoteComposeOp
return toString();
}
+ @Override
+ public Integer getContentDescriptionId() {
+ return mContentDescription;
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 e2502feb2bb1..891367e33d87 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
@@ -50,6 +50,7 @@ public class ShaderData extends Operation implements VariableSupport {
@Nullable HashMap<String, float[]> mUniformFloatMap = null;
@Nullable HashMap<String, int[]> mUniformIntMap;
@Nullable HashMap<String, Integer> mUniformBitmapMap = null;
+ private boolean mShaderValid = false;
public ShaderData(
int shaderID,
@@ -198,6 +199,11 @@ public class ShaderData extends Operation implements VariableSupport {
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -353,7 +359,9 @@ public class ShaderData extends Operation implements VariableSupport {
@Override
public void apply(@NonNull RemoteContext context) {
- context.loadShader(mShaderID, this);
+ if (mShaderValid) {
+ context.loadShader(mShaderID, this);
+ }
}
@NonNull
@@ -361,4 +369,13 @@ public class ShaderData extends Operation implements VariableSupport {
public String deepToString(@NonNull String indent) {
return indent + toString();
}
+
+ /**
+ * Enable or disable the shader
+ *
+ * @param shaderValid if true shader can be used
+ */
+ public void enable(boolean shaderValid) {
+ mShaderValid = shaderValid;
+ }
}
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 3f679bf47582..d48de37996ee 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
@@ -54,6 +54,11 @@ public class TextData extends Operation implements SerializableToString {
return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\"";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 4d01e0c3cbe4..cc0ff025f09b 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
@@ -122,6 +122,11 @@ public class TextFromFloat extends Operation implements VariableSupport {
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 3ec6f019c358..dceb8b67ec3a 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
@@ -79,6 +79,11 @@ public class TextLookup extends Operation implements VariableSupport {
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 9c0ee535f62a..823b70656c86 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
@@ -72,6 +72,11 @@ public class TextLookupInt extends Operation implements VariableSupport {
context.listensTo(mIndex, this);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 5b0c38fe996b..d69561566b56 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
@@ -53,6 +53,11 @@ public class TextMerge extends Operation {
return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index e329c38daf20..e9aae1ebad45 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -64,6 +64,7 @@ public class Theme extends Operation implements RemoteComposeOperation {
@Override
public void apply(@NonNull RemoteContext context) {
context.setTheme(mTheme);
+ markDirty();
}
@NonNull
@@ -72,6 +73,11 @@ public class Theme extends Operation implements RemoteComposeOperation {
return indent + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
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 e2e20bc5e57f..14b72af84e66 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
@@ -21,6 +21,7 @@ import static com.android.internal.widget.remotecompose.core.documentation.Docum
import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
@@ -30,6 +31,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.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
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.operations.utilities.touch.VelocityEasing;
@@ -80,14 +82,41 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
int mTouchEffects;
float mVelocityId;
+ /** Stop with some deceleration */
public static final int STOP_GENTLY = 0;
+
+ /** Stop only at the start or end */
public static final int STOP_ENDS = 2;
+
+ /** Stop on touch up */
public static final int STOP_INSTANTLY = 1;
+
+ /** Stop at evenly spaced notches */
public static final int STOP_NOTCHES_EVEN = 3;
+
+ /** Stop at a collection points described in percents of the range */
public static final int STOP_NOTCHES_PERCENTS = 4;
+
+ /** Stop at a collectiond of point described in abslute cordnates */
public static final int STOP_NOTCHES_ABSOLUTE = 5;
+
+ /** Jump to the absloute poition of the point */
public static final int STOP_ABSOLUTE_POS = 6;
+ /**
+ * create a touch expression
+ *
+ * @param id The float id the value is output to
+ * @param exp the expression (containing TOUCH_* )
+ * @param defValue the default value
+ * @param min the minimum value
+ * @param max the maximum value
+ * @param touchEffects the type of touch mode
+ * @param velocityId the valocity (not used)
+ * @param stopMode the behavour on touch oup
+ * @param stopSpec the paraameters that affect the touch up behavour
+ * @param easingSpec the easing parameters for coming to a stop
+ */
public TouchExpression(
int id,
float[] exp,
@@ -129,7 +158,6 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
@Override
public void updateVariables(RemoteContext context) {
-
if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
mPreCalcValue = new float[mSrcExp.length];
}
@@ -192,7 +220,9 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
if (Float.isNaN(mDefValue)) {
context.listensTo(Utils.idFromNan(mDefValue), this);
}
- context.addTouchListener(this);
+ if (mComponent == null) {
+ context.addTouchListener(this);
+ }
for (float v : mSrcExp) {
if (Float.isNaN(v)
&& !AnimatedFloatExpression.isMathOperator(v)
@@ -332,9 +362,26 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
float mScrLeft, mScrRight, mScrTop, mScrBottom;
- @Override
- public void apply(RemoteContext context) {
- Component comp = context.mLastComponent;
+ @Nullable Component mComponent;
+
+ /**
+ * Set the component the touch expression is in (if any)
+ *
+ * @param component the component, or null if outside
+ */
+ public void setComponent(@Nullable Component component) {
+ mComponent = component;
+ if (mComponent != null) {
+ try {
+ RootLayoutComponent root = mComponent.getRoot();
+ root.setHasTouchListeners(true);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ private void updateBounds() {
+ Component comp = mComponent;
if (comp != null) {
float x = comp.getX();
float y = comp.getY();
@@ -351,7 +398,11 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
mScrRight = w + x;
mScrBottom = h + y;
}
- updateVariables(context);
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ updateBounds();
if (mUnmodified) {
mCurrentValue = mOutDefValue;
context.loadFloat(mId, wrap(mCurrentValue));
@@ -371,6 +422,7 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
mEasingToStop = false;
}
crossNotchCheck(context);
+ context.needsRepaint();
return;
}
if (mTouchDown) {
@@ -395,11 +447,11 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
@Override
public void touchDown(RemoteContext context, float x, float y) {
-
if (!(x >= mScrLeft && x <= mScrRight && y >= mScrTop && y <= mScrBottom)) {
Utils.log("NOT IN WINDOW " + x + ", " + y + " " + mScrLeft + ", " + mScrTop);
return;
}
+ mEasingToStop = false;
mTouchDown = true;
mUnmodified = false;
if (mMode == 0) {
@@ -407,6 +459,7 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
mDownTouchValue =
mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
}
+ context.needsRepaint();
}
@Override
@@ -441,6 +494,7 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
mEasingToStop = true;
+ context.needsRepaint();
}
@Override
@@ -449,7 +503,7 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
return;
}
apply(context);
- context.getDocument().getRootLayoutComponent().needsRepaint();
+ context.needsRepaint();
}
@Override
@@ -494,6 +548,12 @@ public class TouchExpression extends Operation implements VariableSupport, Touch
// ===================== static ======================
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return CLASS_NAME;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
index 0f840597e3c6..1c241601765b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
@@ -23,8 +23,23 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin
/** Operations representing actions on the document */
public interface ActionOperation {
+ /**
+ * Serialize the string
+ *
+ * @param indent padding to display
+ * @param serializer append the string
+ */
void serializeToString(int indent, @NonNull StringSerializer serializer);
+ /**
+ * Run the action
+ *
+ * @param context remote context
+ * @param document document
+ * @param component component
+ * @param x the x location of the action
+ * @param y the y location of the action
+ */
void runAction(
@NonNull RemoteContext context,
@NonNull CoreDocument document,
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 19f4c2b04956..652ab2bc1cbc 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
@@ -20,6 +20,7 @@ import com.android.internal.widget.remotecompose.core.operations.Utils;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+/** Value animation for layouts */
public class AnimatableValue {
boolean mIsVariable = false;
int mId = 0;
@@ -34,6 +35,11 @@ public class AnimatableValue {
int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
FloatAnimation mMotionEasing;
+ /**
+ * Value to animate
+ *
+ * @param value value
+ */
public AnimatableValue(float value) {
if (Utils.isVariable(value)) {
mId = Utils.idFromNan(value);
@@ -43,10 +49,21 @@ public class AnimatableValue {
}
}
+ /**
+ * Get the value
+ *
+ * @return the value
+ */
public float getValue() {
return mValue;
}
+ /**
+ * Evaluate going through FloatAnimation if needed
+ *
+ * @param context the paint context
+ * @return the current value
+ */
public float evaluate(PaintContext context) {
if (!mIsVariable) {
return mValue;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
index 121b18014a21..34b7a2326a21 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
@@ -41,6 +41,11 @@ public class CanvasContent extends Component implements ComponentStartOperation
super(parent, componentId, animationId, x, y, width, height);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "CanvasContent";
@@ -77,6 +82,11 @@ public class CanvasContent extends Component implements ComponentStartOperation
operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1));
}
+ /**
+ * 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())
.field(INT, "COMPONENT_ID", "unique id for this component")
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 34c42494d964..dcf1d250b2f5 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
@@ -16,6 +16,7 @@
package com.android.internal.widget.remotecompose.core.operations.layout;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
@@ -33,13 +34,15 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Color
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
import java.util.ArrayList;
import java.util.List;
/** Represents a click modifier + actions */
public class ClickModifierOperation extends PaintOperation
- implements ModifierOperation, DecoratorComponent, ClickHandler {
+ implements ModifierOperation, DecoratorComponent, ClickHandler, AccessibleComponent {
private static final int OP_CODE = Operations.MODIFIER_CLICK;
long mAnimateRippleStart = 0;
@@ -54,6 +57,22 @@ public class ClickModifierOperation extends PaintOperation
@NonNull PaintBundle mPaint = new PaintBundle();
+ @Override
+ public boolean isClickable() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Role getRole() {
+ return Role.BUTTON;
+ }
+
+ @Override
+ public CoreSemantics.Mode getMode() {
+ return CoreSemantics.Mode.MERGE;
+ }
+
public void animateRipple(float x, float y) {
mAnimateRippleStart = System.currentTimeMillis();
mAnimateRippleX = x;
@@ -80,6 +99,10 @@ public class ClickModifierOperation extends PaintOperation
@Override
public void apply(@NonNull RemoteContext context) {
+ RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+ if (root != null) {
+ root.setHasTouchListeners(true);
+ }
for (Operation op : mList) {
if (op instanceof TextData) {
op.apply(context);
@@ -136,7 +159,8 @@ public class ClickModifierOperation extends PaintOperation
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
mWidth = width;
mHeight = height;
}
@@ -173,6 +197,11 @@ public class ClickModifierOperation extends PaintOperation
context.hapticEffect(3);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "ClickModifier";
@@ -192,6 +221,11 @@ public class ClickModifierOperation extends PaintOperation
operations.add(new ClickModifierOperation());
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, name())
.description(
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 faa259f6b835..e95dfdaa4cf9 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
@@ -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.operations.ComponentValue;
import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimateMeasure;
import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -119,6 +120,17 @@ public class Component extends PaintOperation implements Measurable, Serializabl
mHeight = value;
}
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ for (Operation op : mList) {
+ if (op instanceof VariableSupport && op.isDirty()) {
+ op.markNotDirty();
+ ((VariableSupport) op).updateVariables(context);
+ }
+ }
+ super.apply(context);
+ }
+
/**
* Utility function to update variables referencing this component dimensions
*
@@ -233,14 +245,6 @@ public class Component extends PaintOperation implements Measurable, Serializabl
if (!mComponentValues.isEmpty()) {
updateComponentValues(context);
}
- for (Operation o : mList) {
- if (o instanceof Component) {
- ((Component) o).updateVariables(context);
- }
- if (o instanceof VariableSupport) {
- o.apply(context);
- }
- }
context.mLastComponent = prev;
}
@@ -248,14 +252,40 @@ public class Component extends PaintOperation implements Measurable, Serializabl
mComponentValues.add(v);
}
- public float intrinsicWidth() {
+ /**
+ * Returns the intrinsic width of the layout
+ *
+ * @param context
+ * @return the width in pixels
+ */
+ public float intrinsicWidth(@Nullable RemoteContext context) {
return getWidth();
}
- public float intrinsicHeight() {
+ /**
+ * Returns the intrinsic height of the layout
+ *
+ * @param context
+ * @return the height in pixels
+ */
+ public float intrinsicHeight(@Nullable RemoteContext context) {
return getHeight();
}
+ /**
+ * This function is called after a component is created, with its mList initialized. This let
+ * the component a chance to do some post-initialization work on its children ops.
+ */
+ public void inflate() {
+ for (Operation op : mList) {
+ if (op instanceof TouchExpression) {
+ // Make sure to set the component of a touch expression that belongs to us!
+ TouchExpression touchExpression = (TouchExpression) op;
+ touchExpression.setComponent(this);
+ }
+ }
+ }
+
public enum Visibility {
GONE,
VISIBLE,
@@ -409,11 +439,23 @@ public class Component extends PaintOperation implements Measurable, Serializabl
if (op instanceof TouchHandler) {
((TouchHandler) op).onTouchDown(context, document, this, cx, cy);
}
+ if (op instanceof TouchExpression) {
+ TouchExpression touchExpression = (TouchExpression) op;
+ touchExpression.updateVariables(context);
+ touchExpression.touchDown(context, cx, cy);
+ document.appliedTouchOperation(this);
+ }
}
}
public void onTouchUp(
- RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+ RemoteContext context,
+ CoreDocument document,
+ float x,
+ float y,
+ float dx,
+ float dy,
+ boolean force) {
if (!force && !contains(x, y)) {
return;
}
@@ -421,10 +463,15 @@ public class Component extends PaintOperation implements Measurable, Serializabl
float cy = y - getScrollY();
for (Operation op : mList) {
if (op instanceof Component) {
- ((Component) op).onTouchUp(context, document, cx, cy, force);
+ ((Component) op).onTouchUp(context, document, cx, cy, dx, dy, force);
}
if (op instanceof TouchHandler) {
- ((TouchHandler) op).onTouchUp(context, document, this, cx, cy);
+ ((TouchHandler) op).onTouchUp(context, document, this, cx, cy, dx, dy);
+ }
+ if (op instanceof TouchExpression) {
+ TouchExpression touchExpression = (TouchExpression) op;
+ touchExpression.updateVariables(context);
+ touchExpression.touchUp(context, cx, cy, dx, dy);
}
}
}
@@ -443,6 +490,11 @@ public class Component extends PaintOperation implements Measurable, Serializabl
if (op instanceof TouchHandler) {
((TouchHandler) op).onTouchCancel(context, document, this, cx, cy);
}
+ if (op instanceof TouchExpression) {
+ TouchExpression touchExpression = (TouchExpression) op;
+ touchExpression.updateVariables(context);
+ touchExpression.touchUp(context, cx, cy, 0, 0);
+ }
}
}
@@ -460,6 +512,11 @@ public class Component extends PaintOperation implements Measurable, Serializabl
if (op instanceof TouchHandler) {
((TouchHandler) op).onTouchDrag(context, document, this, cx, cy);
}
+ if (op instanceof TouchExpression) {
+ TouchExpression touchExpression = (TouchExpression) op;
+ touchExpression.updateVariables(context);
+ touchExpression.touchDrag(context, x, y);
+ }
}
}
@@ -641,6 +698,9 @@ public class Component extends PaintOperation implements Measurable, Serializabl
}
public void paintingComponent(@NonNull PaintContext context) {
+ if (!mComponentValues.isEmpty()) {
+ updateComponentValues(context.getContext());
+ }
if (mPreTranslate != null) {
mPreTranslate.paint(context);
}
@@ -652,7 +712,15 @@ public class Component extends PaintOperation implements Measurable, Serializabl
debugBox(this, context);
}
for (Operation op : mList) {
- op.apply(context.getContext());
+ if (op.isDirty() && op instanceof VariableSupport) {
+ ((VariableSupport) op).updateVariables(context.getContext());
+ op.markNotDirty();
+ }
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ } else {
+ op.apply(context.getContext());
+ }
}
context.restore();
context.getContext().mLastComponent = prev;
@@ -661,7 +729,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl
public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
if (context.isAnimationEnabled() && mAnimateMeasure != null) {
mAnimateMeasure.apply(context);
- needsRepaint();
+ context.needsRepaint();
return true;
}
return false;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
index 396644c45fa4..5da06634d101 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -49,6 +49,11 @@ public class ComponentEnd extends Operation {
return (indent != null ? indent : "") + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "ComponentEnd";
@@ -81,6 +86,11 @@ public class ComponentEnd extends Operation {
operations.add(new ComponentEnd());
}
+ /**
+ * 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(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
index a85ae270ffb1..4349b31d76e3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -157,6 +157,11 @@ public class ComponentStart extends Operation implements ComponentStartOperation
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "ComponentStart";
@@ -198,6 +203,11 @@ public class ComponentStart extends Operation implements ComponentStartOperation
operations.add(new ComponentStart(type, componentId, width, height));
}
+ /**
+ * 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(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
index d6170074238a..9ca2f2ed3ba7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -24,5 +24,13 @@ import com.android.internal.widget.remotecompose.core.RemoteContext;
* measured. Eg borders, background, clips, etc.
*/
public interface DecoratorComponent {
- void layout(@NonNull RemoteContext context, float width, float height);
+ /**
+ * Layout the decorator
+ *
+ * @param context
+ * @param component the associated component
+ * @param width horizontal dimension in pixels
+ * @param height vertical dimension in pixels
+ */
+ void layout(@NonNull RemoteContext context, Component component, float width, float height);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index 7b0e4a2e2627..e25392c5d2ff 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -21,6 +21,8 @@ import android.annotation.Nullable;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.OperationInterface;
import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
@@ -36,6 +38,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
@@ -54,6 +57,12 @@ public class LayoutComponent extends Component {
protected float mPaddingTop = 0f;
protected float mPaddingBottom = 0f;
+ float mScrollX = 0f;
+ float mScrollY = 0f;
+
+ @Nullable protected ScrollDelegate mHorizontalScrollDelegate = null;
+ @Nullable protected ScrollDelegate mVerticalScrollDelegate = null;
+
@NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
@NonNull
@@ -111,6 +120,7 @@ public class LayoutComponent extends Component {
// Should be removed after ImageLayout is in
private static final boolean USE_IMAGE_TEMP_FIX = true;
+ @Override
public void inflate() {
ArrayList<TextData> data = new ArrayList<>();
ArrayList<Operation> supportedOperations = new ArrayList<>();
@@ -144,6 +154,7 @@ public class LayoutComponent extends Component {
if (!canvasContent.mList.isEmpty()) {
mContent.mList.clear();
mChildrenComponents.add(canvasContent);
+ canvasContent.inflate();
}
} else {
content.getData(data);
@@ -155,6 +166,9 @@ public class LayoutComponent extends Component {
if (op instanceof ComponentVisibilityOperation) {
((ComponentVisibilityOperation) op).setParent(this);
}
+ if (op instanceof ScrollModifierOperation) {
+ ((ScrollModifierOperation) op).inflate(this);
+ }
mComponentModifiers.add((ModifierOperation) op);
} else if (op instanceof TextData) {
data.add((TextData) op);
@@ -162,6 +176,9 @@ public class LayoutComponent extends Component {
|| (op instanceof PaintData)
|| (op instanceof FloatExpression)) {
supportedOperations.add(op);
+ if (op instanceof TouchExpression) {
+ ((TouchExpression) op).setComponent(this);
+ }
} else {
// nothing
}
@@ -186,8 +203,6 @@ public class LayoutComponent extends Component {
mPaddingRight = 0f;
mPaddingBottom = 0f;
- boolean applyHorizontalMargin = true;
- boolean applyVerticalMargin = true;
for (OperationInterface op : mComponentModifiers.getList()) {
if (op instanceof PaddingModifierOperation) {
// We are accumulating padding modifiers to compute the margin
@@ -209,6 +224,14 @@ public class LayoutComponent extends Component {
mZIndexModifier = (ZIndexModifierOperation) op;
} else if (op instanceof GraphicsLayerModifierOperation) {
mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+ } else if (op instanceof ScrollDelegate) {
+ ScrollDelegate scrollDelegate = (ScrollDelegate) op;
+ if (scrollDelegate.handlesHorizontalScroll()) {
+ mHorizontalScrollDelegate = scrollDelegate;
+ }
+ if (scrollDelegate.handlesVerticalScroll()) {
+ mVerticalScrollDelegate = scrollDelegate;
+ }
}
}
if (mWidthModifier == null) {
@@ -217,8 +240,8 @@ public class LayoutComponent extends Component {
if (mHeightModifier == null) {
mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
}
- setWidth(computeModifierDefinedWidth());
- setHeight(computeModifierDefinedHeight());
+ setWidth(computeModifierDefinedWidth(null));
+ setHeight(computeModifierDefinedHeight(null));
}
@NonNull
@@ -228,13 +251,36 @@ public class LayoutComponent extends Component {
}
@Override
+ public void getLocationInWindow(@NonNull float[] value) {
+ value[0] += mX + mPaddingLeft;
+ value[1] += mY + mPaddingTop;
+ if (mParent != null) {
+ mParent.getLocationInWindow(value);
+ }
+ }
+
+ @Override
public float getScrollX() {
- return mComponentModifiers.getScrollX();
+ if (mHorizontalScrollDelegate != null) {
+ return mHorizontalScrollDelegate.getScrollX(mScrollX);
+ }
+ return mScrollX;
+ }
+
+ public void setScrollX(float value) {
+ mScrollX = value;
}
@Override
public float getScrollY() {
- return mComponentModifiers.getScrollY();
+ if (mVerticalScrollDelegate != null) {
+ return mVerticalScrollDelegate.getScrollY(mScrollY);
+ }
+ return mScrollY;
+ }
+
+ public void setScrollY(float value) {
+ mScrollY = value;
}
@Override
@@ -279,10 +325,18 @@ public class LayoutComponent extends Component {
ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents);
sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex()));
for (Component child : sorted) {
+ if (child.isDirty() && child instanceof VariableSupport) {
+ child.updateVariables(context.getContext());
+ child.markNotDirty();
+ }
child.paint(context);
}
} else {
for (Component child : mChildrenComponents) {
+ if (child.isDirty() && child instanceof VariableSupport) {
+ child.updateVariables(context.getContext());
+ child.markNotDirty();
+ }
child.paint(context);
}
}
@@ -295,11 +349,15 @@ public class LayoutComponent extends Component {
}
/** Traverse the modifiers to compute indicated dimension */
- public float computeModifierDefinedWidth() {
+ public float computeModifierDefinedWidth(@Nullable RemoteContext context) {
float s = 0f;
float e = 0f;
float w = 0f;
for (OperationInterface c : mComponentModifiers.getList()) {
+ if (context != null && c.isDirty() && c instanceof VariableSupport) {
+ ((VariableSupport) c).updateVariables(context);
+ c.markNotDirty();
+ }
if (c instanceof WidthModifierOperation) {
WidthModifierOperation o = (WidthModifierOperation) c;
if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -339,11 +397,15 @@ public class LayoutComponent extends Component {
}
/** Traverse the modifiers to compute indicated dimension */
- public float computeModifierDefinedHeight() {
+ public float computeModifierDefinedHeight(@Nullable RemoteContext context) {
float t = 0f;
float b = 0f;
float h = 0f;
for (OperationInterface c : mComponentModifiers.getList()) {
+ if (context != null && c.isDirty() && c instanceof VariableSupport) {
+ ((VariableSupport) c).updateVariables(context);
+ c.markNotDirty();
+ }
if (c instanceof HeightModifierOperation) {
HeightModifierOperation o = (HeightModifierOperation) c;
if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -383,6 +445,11 @@ public class LayoutComponent extends Component {
}
@NonNull
+ public ComponentModifiers getComponentModifiers() {
+ return mComponentModifiers;
+ }
+
+ @NonNull
public ArrayList<Component> getChildrenComponents() {
return mChildrenComponents;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
index 20e4688aaa32..9bfbe6a42a37 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
@@ -41,6 +41,11 @@ public class LayoutComponentContent extends Component implements ComponentStartO
super(parent, componentId, animationId, x, y, width, height);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "LayoutContent";
@@ -77,6 +82,11 @@ public class LayoutComponentContent extends Component implements ComponentStartO
operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1));
}
+ /**
+ * 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())
.field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
index df960e45736e..505656e212f1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
@@ -32,8 +32,8 @@ public abstract class ListActionsOperation extends PaintOperation
implements ModifierOperation, DecoratorComponent {
String mOperationName;
- float mWidth = 0;
- float mHeight = 0;
+ protected float mWidth = 0;
+ protected float mHeight = 0;
private final float[] mLocationInWindow = new float[2];
@@ -71,7 +71,7 @@ public abstract class ListActionsOperation extends PaintOperation
public void paint(PaintContext context) {}
@Override
- public void layout(RemoteContext context, float width, float height) {
+ public void layout(RemoteContext context, Component component, float width, float height) {
mWidth = width;
mHeight = height;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
index d88f711c62c6..3d389e5badef 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
@@ -49,6 +49,11 @@ public class LoopEnd extends Operation {
return (indent != null ? indent : "") + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "LoopEnd";
@@ -77,6 +82,11 @@ public class LoopEnd extends Operation {
operations.add(new LoopEnd());
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Operations", id(), name()).description("End tag for loops");
}
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 83a2f0e1ffa3..1b85681bcc08 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
@@ -117,7 +117,7 @@ public class LoopOperation extends PaintOperation implements VariableSupport {
for (float i = mFromOut; i < mUntilOut; i += mStepOut) {
context.getContext().loadFloat(mIndexVariableId, i);
for (Operation op : mList) {
- if (op instanceof VariableSupport) {
+ if (op instanceof VariableSupport && op.isDirty()) {
((VariableSupport) op).updateVariables(context.getContext());
}
op.apply(context.getContext());
@@ -126,6 +126,11 @@ public class LoopOperation extends PaintOperation implements VariableSupport {
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "Loop";
@@ -154,6 +159,11 @@ public class LoopOperation extends PaintOperation implements VariableSupport {
operations.add(new LoopOperation(indexId, from, step, until));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Operations", OP_CODE, name())
.description("Loop. This operation execute" + " a list of action in a loop")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
index 99b7e68786fb..12a673d7380f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
@@ -49,6 +49,11 @@ public class OperationsListEnd extends Operation {
return (indent != null ? indent : "") + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "ListEnd";
@@ -77,6 +82,11 @@ public class OperationsListEnd extends Operation {
operations.add(new OperationsListEnd());
}
+ /**
+ * 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("End tag for list of operations.");
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 fd1628729dd4..11c0f3f394f5 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
@@ -192,6 +192,11 @@ public class RootLayoutComponent extends Component implements ComponentStartOper
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "RootLayout";
@@ -222,6 +227,11 @@ public class RootLayoutComponent extends Component implements ComponentStartOper
operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1));
}
+ /**
+ * 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())
.field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java
new file mode 100644
index 000000000000..7ef9766ba077
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+/**
+ * Represent scroll delegates components.
+ *
+ * <p>Components have scroll X & Y properties. We can inject a scroll delegate as a modifier (e.g. a
+ * scrollView, a marquee...) to control the value of those properties.
+ */
+public interface ScrollDelegate {
+
+ /**
+ * Returns the horizontal scroll value
+ *
+ * @param currentValue the current value
+ * @return the value set by the delegate
+ */
+ float getScrollX(float currentValue);
+
+ /**
+ * Returns the vertical scroll value
+ *
+ * @param currentValue the current value
+ * @return the value set by the delegate
+ */
+ float getScrollY(float currentValue);
+
+ /**
+ * Returns true if the delegate can handle horizontal scroll
+ *
+ * @return true if the delegate handles horizontal scrolling
+ */
+ boolean handlesHorizontalScroll();
+
+ /**
+ * Returns true if the delegate can handle vertical scroll
+ *
+ * @return true if the delegate handles vertical scrolling
+ */
+ boolean handlesVerticalScroll();
+
+ /** Reset the delegate (e.g. the content of the component has changed) */
+ void reset();
+}
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 3185bb5f0f72..4977a15e2dc1 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
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core.operations.layout;
+import android.annotation.NonNull;
+
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
@@ -60,7 +62,13 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement
@Override
public void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {
// nothing
}
@@ -76,6 +84,12 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement
// nothing
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return "TouchCancelModifier";
}
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 d98911f82060..8c51f2eac383 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
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core.operations.layout;
+import android.annotation.NonNull;
+
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
@@ -62,7 +64,13 @@ public class TouchDownModifierOperation extends ListActionsOperation implements
@Override
public void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {
// nothing
}
@@ -78,6 +86,12 @@ public class TouchDownModifierOperation extends ListActionsOperation implements
// nothing
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return "TouchModifier";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
index ac9dd908d6a4..607060e51496 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
@@ -41,9 +41,17 @@ public interface TouchHandler {
* @param component the component on which the touch has been received
* @param x the x position of the click in document coordinates
* @param y the y position of the click in document coordinates
+ * @param dx
+ * @param dy
*/
void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y);
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy);
/**
* callback for a touch move event
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 f6cb3750906f..a12c356f7c48 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
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core.operations.layout;
+import android.annotation.NonNull;
+
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
@@ -60,7 +62,13 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To
@Override
public void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {
applyActions(context, document, component, x, y, true);
}
@@ -76,6 +84,12 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To
// nothing
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return "TouchUpModifier";
}
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 b3430998a520..2af3c739035c 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
@@ -120,7 +120,7 @@ public class AnimateMeasure {
h -= pop.getTop() + pop.getBottom();
}
if (op instanceof DecoratorComponent) {
- ((DecoratorComponent) op).layout(context.getContext(), w, h);
+ ((DecoratorComponent) op).layout(context.getContext(), mComponent, w, h);
}
}
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 b230b09112b2..6dff4a87088b 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
@@ -137,6 +137,11 @@ public class AnimationSpec extends Operation {
return (indent != null ? indent : "") + toString();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "AnimationSpec";
@@ -224,6 +229,11 @@ public class AnimationSpec extends Operation {
operations.add(op);
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", id(), name())
.description("define the animation")
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 01cd7ccd0b94..8076cb10ea0c 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
@@ -119,8 +119,9 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation
size.setHeight(Math.max(size.getHeight(), m.getH()));
}
// add padding
- size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth()));
- size.setHeight(Math.max(size.getHeight(), computeModifierDefinedHeight()));
+ size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+ size.setHeight(
+ Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
}
@Override
@@ -172,6 +173,11 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation
}
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "BoxLayout";
@@ -219,6 +225,11 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation
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(
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 665db2637674..0091a47eebfb 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
@@ -72,6 +72,11 @@ public class CanvasLayout extends BoxLayout {
return "CANVAS";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "CanvasLayout";
@@ -104,6 +109,11 @@ public class CanvasLayout extends BoxLayout {
operations.add(new CanvasLayout(null, componentId, animationId));
}
+ /**
+ * 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("Canvas implementation. Encapsulate draw operations.\n\n")
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 5b9ee0ff511f..249e84a1c1bc 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
@@ -24,6 +24,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.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -169,11 +170,11 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati
}
@Override
- public float intrinsicHeight() {
- float height = computeModifierDefinedHeight();
+ public float intrinsicHeight(@NonNull RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
float componentHeights = 0f;
for (Component c : mChildrenComponents) {
- componentHeights += c.intrinsicHeight();
+ componentHeights += c.intrinsicHeight(context);
}
return Math.max(height, componentHeights);
}
@@ -341,6 +342,11 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati
DebugLog.e();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "ColumnLayout";
@@ -392,6 +398,11 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati
spacedBy));
}
+ /**
+ * 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(
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 6a15b7f1b178..a5edaa8de3af 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
@@ -61,19 +61,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
}
@Override
- public float intrinsicHeight() {
- float height = computeModifierDefinedHeight();
+ public float intrinsicHeight(@Nullable RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
for (Component c : mChildrenComponents) {
- height = Math.max(c.intrinsicHeight(), height);
+ height = Math.max(c.intrinsicHeight(context), height);
}
return height;
}
@Override
- public float intrinsicWidth() {
- float width = computeModifierDefinedWidth();
+ public float intrinsicWidth(@Nullable RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
for (Component c : mChildrenComponents) {
- width = Math.max(c.intrinsicWidth(), width);
+ width = Math.max(c.intrinsicWidth(context), width);
}
return width;
}
@@ -132,16 +132,17 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
@NonNull MeasurePass measure) {
boolean hasWrap = true;
- float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth());
- float measuredHeight = Math.min(maxHeight, computeModifierDefinedHeight());
+ float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth(context.getContext()));
+ float measuredHeight =
+ Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom;
if (mWidthModifier.isIntrinsicMin()) {
- maxWidth = intrinsicWidth();
+ maxWidth = intrinsicWidth(context.getContext());
}
if (mHeightModifier.isIntrinsicMin()) {
- maxHeight = intrinsicHeight();
+ maxHeight = intrinsicHeight(context.getContext());
}
boolean hasHorizontalWrap = mWidthModifier.isWrap();
@@ -180,7 +181,8 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
if (isInHorizontalFill()) {
measuredWidth = maxWidth;
} else if (mWidthModifier.hasWeight()) {
- measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
+ measuredWidth =
+ Math.max(measuredWidth, computeModifierDefinedWidth(context.getContext()));
} else {
measuredWidth = Math.max(measuredWidth, minWidth);
measuredWidth = Math.min(measuredWidth, maxWidth);
@@ -188,7 +190,8 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
if (isInVerticalFill()) { // todo: potential npe -- bbade@
measuredHeight = maxHeight;
} else if (mHeightModifier.hasWeight()) {
- measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
+ measuredHeight =
+ Math.max(measuredHeight, computeModifierDefinedHeight(context.getContext()));
} else {
measuredHeight = Math.max(measuredHeight, minHeight);
measuredHeight = Math.min(measuredHeight, maxHeight);
@@ -224,7 +227,9 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
computeSize(context, 0f, measuredWidth, 0, h, measure);
mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
} else {
- computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+ float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
+ float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
+ computeSize(context, 0f, maxChildWidth, 0f, maxChildHeight, measure);
}
}
@@ -258,7 +263,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
super.layout(context, measure);
ComponentMeasure self = measure.get(this);
- mComponentModifiers.layout(context, self.getW(), self.getH());
+ mComponentModifiers.layout(context, this, self.getW(), self.getH());
for (Component c : mChildrenComponents) {
c.layout(context, measure);
}
@@ -275,7 +280,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
super.layout(context, measure);
ComponentMeasure self = measure.get(this);
- mComponentModifiers.layout(context, self.getW(), self.getH());
+ mComponentModifiers.layout(context, this, self.getW(), self.getH());
this.mNeedsMeasure = false;
}
}
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 0ec820b85964..37b9a688af8b 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
@@ -24,6 +24,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.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -167,11 +168,11 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation
}
@Override
- public float intrinsicWidth() {
- float width = computeModifierDefinedWidth();
+ public float intrinsicWidth(@Nullable RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
float componentWidths = 0f;
for (Component c : mChildrenComponents) {
- componentWidths += c.intrinsicWidth();
+ componentWidths += c.intrinsicWidth(context);
}
return Math.max(width, componentWidths);
}
@@ -344,6 +345,11 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation
DebugLog.e();
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "RowLayout";
@@ -395,6 +401,11 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation
spacedBy));
}
+ /**
+ * 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(
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 8e7f538d0004..910205e8a7e2 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
@@ -34,11 +34,13 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure.
import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
import java.util.List;
/** Text component, referencing a text id */
-public class TextLayout extends LayoutManager implements ComponentStartOperation, VariableSupport {
+public class TextLayout extends LayoutManager
+ implements ComponentStartOperation, VariableSupport, AccessibleComponent {
private static final boolean DEBUG = false;
private int mTextId = -1;
@@ -57,6 +59,12 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation
@Nullable private String mCachedString = "";
+ @Nullable
+ @Override
+ public Integer getTextId() {
+ return mTextId;
+ }
+
@Override
public void registerListening(@NonNull RemoteContext context) {
if (mTextId != -1) {
@@ -92,6 +100,13 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation
}
mTextW = -1;
mTextH = -1;
+
+ if (mHorizontalScrollDelegate != null) {
+ mHorizontalScrollDelegate.reset();
+ }
+ if (mVerticalScrollDelegate != null) {
+ mVerticalScrollDelegate.reset();
+ }
invalidateMeasure();
}
@@ -175,6 +190,11 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation
int length = mCachedString.length();
if (mTextW > mWidth) {
context.save();
+ context.clipRect(
+ mPaddingLeft,
+ mPaddingTop,
+ mWidth - mPaddingLeft - mPaddingRight,
+ mHeight - mPaddingTop - mPaddingBottom);
context.translate(getScrollX(), getScrollY());
context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
context.restore();
@@ -285,15 +305,20 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation
}
@Override
- public float intrinsicHeight() {
+ public float intrinsicHeight(@Nullable RemoteContext context) {
return mTextH;
}
@Override
- public float intrinsicWidth() {
+ public float intrinsicWidth(@Nullable RemoteContext context) {
return mTextW;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "TextLayout";
@@ -361,6 +386,11 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation
textAlign));
}
+ /**
+ * 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("Text layout implementation.\n\n")
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 5df16c5bc03a..b4240d0e08a7 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
@@ -25,6 +25,7 @@ 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.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
@@ -98,7 +99,8 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
this.mWidth = width;
this.mHeight = height;
}
@@ -109,6 +111,11 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -182,6 +189,11 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
context.restorePaint();
}
+ /**
+ * 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("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the Background Modifier")
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 bfadd2f1ef9c..df30d9f615e5 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
@@ -25,6 +25,7 @@ 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.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
@@ -124,7 +125,8 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
this.mWidth = width;
this.mHeight = height;
}
@@ -155,6 +157,11 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
+ ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -240,6 +247,11 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
context.restorePaint();
}
+ /**
+ * 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("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the Border Modifier")
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 d0af872acc53..b27fb9200398 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
@@ -23,6 +23,7 @@ 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.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
@@ -40,7 +41,8 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
this.mWidth = width;
this.mHeight = height;
}
@@ -55,6 +57,11 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation {
apply(buffer);
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -83,6 +90,11 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation {
operations.add(new ClipRectModifierOperation());
}
+ /**
+ * 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("Canvas Operations", OP_CODE, CLASS_NAME)
.description("Draw the specified round-rect");
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 d11f26f83ebd..d2ba13f69a91 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
@@ -21,6 +21,7 @@ import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
@@ -86,6 +87,10 @@ public class ComponentModifiers extends PaintOperation
float tx = 0f;
float ty = 0f;
for (ModifierOperation op : mList) {
+ if (op.isDirty() && op instanceof VariableSupport) {
+ ((VariableSupport) op).updateVariables(context.getContext());
+ op.markNotDirty();
+ }
if (op instanceof PaddingModifierOperation) {
PaddingModifierOperation pop = (PaddingModifierOperation) op;
context.translate(pop.getLeft(), pop.getTop());
@@ -109,7 +114,8 @@ public class ComponentModifiers extends PaintOperation
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
float w = width;
float h = height;
for (ModifierOperation op : mList) {
@@ -119,9 +125,9 @@ public class ComponentModifiers extends PaintOperation
h -= pop.getTop() + pop.getBottom();
}
if (op instanceof ClickModifierOperation) {
- ((DecoratorComponent) op).layout(context, width, height);
+ ((DecoratorComponent) op).layout(context, component, width, height);
} else if (op instanceof DecoratorComponent) {
- ((DecoratorComponent) op).layout(context, w, h);
+ ((DecoratorComponent) op).layout(context, component, w, h);
}
}
}
@@ -156,10 +162,16 @@ public class ComponentModifiers extends PaintOperation
@Override
public void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {
for (ModifierOperation op : mList) {
if (op instanceof TouchHandler) {
- ((TouchHandler) op).onTouchUp(context, document, component, x, y);
+ ((TouchHandler) op).onTouchUp(context, document, component, x, y, dx, dy);
}
}
}
@@ -208,32 +220,6 @@ public class ComponentModifiers extends PaintOperation
return false;
}
- public float getScrollX() {
- float scroll = 0;
- for (ModifierOperation op : mList) {
- if (op instanceof ScrollModifierOperation) {
- ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
- if (scrollModifier.isHorizontalScroll()) {
- scroll = Math.min(scroll, scrollModifier.getScrollX());
- }
- }
- }
- return scroll;
- }
-
- public float getScrollY() {
- float scroll = 0;
- for (ModifierOperation op : mList) {
- if (op instanceof ScrollModifierOperation) {
- ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
- if (scrollModifier.isVerticalScroll()) {
- scroll = Math.min(scroll, scrollModifier.getScrollY());
- }
- }
- }
- return scroll;
- }
-
public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
for (ModifierOperation op : mList) {
if (op instanceof ScrollModifierOperation) {
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 1e6ccfcb5d34..c377b756ff38 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
@@ -90,6 +90,11 @@ public class ComponentVisibilityOperation extends Operation
operations.add(new ComponentVisibilityOperation(valueId));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ComponentVisibility")
.description(
@@ -125,5 +130,6 @@ public class ComponentVisibilityOperation extends Operation
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {}
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {}
}
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 4252309b7e4c..15c2f46093d2 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
@@ -27,6 +27,7 @@ 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.operations.layout.AnimatableValue;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
@@ -202,6 +203,12 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return CLASS_NAME;
}
@@ -306,5 +313,5 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(RemoteContext context, float width, float height) {}
+ public void layout(RemoteContext context, Component component, float width, float height) {}
}
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 692b5269954a..ec078a9e73ea 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
@@ -32,6 +32,11 @@ public class HeightModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
public static final String CLASS_NAME = "HeightModifierOperation";
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -94,6 +99,11 @@ public class HeightModifierOperation extends DimensionModifierOperation {
return "HEIGHT";
}
+ /**
+ * 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("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the animation")
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 333e281d4abb..2e9d6619d011 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
@@ -99,6 +99,11 @@ public class HostActionOperation extends Operation implements ActionOperation {
operations.add(new HostActionOperation(actionId));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "HostAction")
.description("Host action. This operation represents a host action")
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 f9a4270905a1..49ef58e0fe53 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
@@ -125,6 +125,11 @@ public class HostNamedActionOperation extends Operation implements ActionOperati
operations.add(new HostNamedActionOperation(textId, type, valueId));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "HostNamedAction")
.description("Host Named action. This operation represents a host action")
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
new file mode 100644
index 000000000000..9588e99a65b6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.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.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a Marquee modifier. */
+public class MarqueeModifierOperation extends DecoratorModifierOperation implements ScrollDelegate {
+ private static final int OP_CODE = Operations.MODIFIER_MARQUEE;
+ public static final String CLASS_NAME = "MarqueeModifierOperation";
+
+ int mIterations;
+ int mAnimationMode;
+ float mRepeatDelayMillis;
+ float mInitialDelayMillis;
+ float mSpacing;
+ float mVelocity;
+
+ private float mComponentWidth;
+ private float mComponentHeight;
+ private float mContentWidth;
+ private float mContentHeight;
+
+ public MarqueeModifierOperation(
+ int iterations,
+ int animationMode,
+ float repeatDelayMillis,
+ float initialDelayMillis,
+ float spacing,
+ float velocity) {
+ this.mIterations = iterations;
+ this.mAnimationMode = animationMode;
+ this.mRepeatDelayMillis = repeatDelayMillis;
+ this.mInitialDelayMillis = initialDelayMillis;
+ this.mSpacing = spacing;
+ this.mVelocity = velocity;
+ }
+
+ public void setContentWidth(float value) {
+ mContentWidth = value;
+ }
+
+ public void setContentHeight(float value) {
+ mContentHeight = value;
+ }
+
+ @Override
+ public float getScrollX(float currentValue) {
+ return mScrollX;
+ }
+
+ @Override
+ public float getScrollY(float currentValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean handlesHorizontalScroll() {
+ return true;
+ }
+
+ @Override
+ public boolean handlesVerticalScroll() {
+ return false;
+ }
+
+ /** Reset the modifier */
+ public void reset() {
+ mLastTime = 0;
+ mScrollX = 0f;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ apply(
+ buffer,
+ mIterations,
+ mAnimationMode,
+ mRepeatDelayMillis,
+ mInitialDelayMillis,
+ mSpacing,
+ mVelocity);
+ }
+
+ // @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "MARQUEE = [" + mIterations + "]");
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ private long mLastTime = 0;
+ private long mStartTime = 0;
+
+ private float mScrollX = 0f;
+
+ @Override
+ public void paint(PaintContext context) {
+ long currentTime = System.currentTimeMillis();
+ if (mLastTime == 0) {
+ mLastTime = currentTime;
+ mStartTime = mLastTime + (long) mInitialDelayMillis;
+ context.needsRepaint();
+ }
+ if (mContentWidth > mComponentWidth && currentTime - mStartTime > mInitialDelayMillis) {
+ float density = context.getContext().getDensity(); // in dp
+ float delta = mContentWidth - mComponentWidth;
+ float duration = delta / (density * mVelocity);
+ float elapsed = ((System.currentTimeMillis() - mStartTime) / 1000f);
+ elapsed = (elapsed % duration) / duration;
+ float offset =
+ (1f + (float) Math.sin(elapsed * 2 * Math.PI - Math.PI / 2f)) / 2f * -delta;
+
+ mScrollX = offset;
+ context.needsRepaint();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "MarqueeModifierOperation(" + mIterations + ")";
+ }
+
+ public static String name() {
+ return CLASS_NAME;
+ }
+
+ public static int id() {
+ return OP_CODE;
+ }
+
+ public static void apply(
+ WireBuffer buffer,
+ int iterations,
+ int animationMode,
+ float repeatDelayMillis,
+ float initialDelayMillis,
+ float spacing,
+ float velocity) {
+ buffer.start(OP_CODE);
+ buffer.writeInt(iterations);
+ buffer.writeInt(animationMode);
+ buffer.writeFloat(repeatDelayMillis);
+ buffer.writeFloat(initialDelayMillis);
+ buffer.writeFloat(spacing);
+ buffer.writeFloat(velocity);
+ }
+
+ public static void read(WireBuffer buffer, List<Operation> operations) {
+ int iterations = buffer.readInt();
+ int animationMode = buffer.readInt();
+ float repeatDelayMillis = buffer.readFloat();
+ float initialDelayMillis = buffer.readFloat();
+ float spacing = buffer.readFloat();
+ float velocity = buffer.readFloat();
+ operations.add(
+ new MarqueeModifierOperation(
+ iterations,
+ animationMode,
+ repeatDelayMillis,
+ initialDelayMillis,
+ spacing,
+ velocity));
+ }
+
+ public static void documentation(DocumentationBuilder doc) {
+ doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+ .description("specify a Marquee Modifier")
+ .field(FLOAT, "value", "");
+ }
+
+ @Override
+ public void layout(RemoteContext context, Component component, float width, float height) {
+ mComponentWidth = width;
+ mComponentHeight = height;
+ if (component instanceof LayoutComponent) {
+ LayoutComponent layoutComponent = (LayoutComponent) component;
+ setContentWidth(layoutComponent.intrinsicWidth(context));
+ setContentHeight(layoutComponent.intrinsicHeight(context));
+ }
+ }
+}
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 69c4e9a8e423..42719478faf0 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
@@ -26,6 +26,7 @@ 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.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
@@ -90,6 +91,12 @@ public class OffsetModifierOperation extends DecoratorModifierOperation {
return "OffsetModifierOperation(" + mX + ", " + mY + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return CLASS_NAME;
}
@@ -123,5 +130,5 @@ public class OffsetModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(RemoteContext context, float width, float height) {}
+ public void layout(RemoteContext context, Component component, float width, float height) {}
}
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 545df64ab154..bcfbdd68472f 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
@@ -113,6 +113,11 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera
+ ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -150,6 +155,11 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera
operations.add(new PaddingModifierOperation(left, top, right, bottom));
}
+ /**
+ * 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("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the Padding Modifier")
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
new file mode 100644
index 000000000000..fe074e4754e2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+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.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.ColorUtils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+
+import java.util.List;
+
+/** Represents a ripple effect */
+public class RippleModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+ private static final int OP_CODE = Operations.MODIFIER_RIPPLE;
+
+ long mAnimateRippleStart = 0;
+ float mAnimateRippleX = 0f;
+ float mAnimateRippleY = 0f;
+ int mAnimateRippleDuration = 1000;
+
+ float mWidth = 0;
+ float mHeight = 0;
+
+ @NonNull public float[] locationInWindow = new float[2];
+
+ @NonNull PaintBundle mPaint = new PaintBundle();
+
+ /**
+ * Animate the ripple effect
+ *
+ * @param x
+ * @param y
+ */
+ public void animateRipple(float x, float y) {
+ mAnimateRippleStart = System.currentTimeMillis();
+ mAnimateRippleX = x;
+ mAnimateRippleY = y;
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "RippleModifier";
+ }
+
+ @Override
+ public void apply(@NonNull RemoteContext context) {
+ RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+ if (root != null) {
+ root.setHasTouchListeners(true);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String deepToString(@NonNull String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void paint(@NonNull PaintContext context) {
+ if (mAnimateRippleStart == 0) {
+ return;
+ }
+ context.needsRepaint();
+
+ float progress = (System.currentTimeMillis() - mAnimateRippleStart);
+ progress /= (float) mAnimateRippleDuration;
+ if (progress > 1f) {
+ mAnimateRippleStart = 0;
+ }
+ progress = Math.min(1f, progress);
+ context.save();
+ context.savePaint();
+ mPaint.reset();
+
+ FloatAnimation anim1 =
+ new FloatAnimation(Easing.CUBIC_STANDARD, 1f, null, Float.NaN, Float.NaN);
+ anim1.setInitialValue(0f);
+ anim1.setTargetValue(1f);
+ float tween = anim1.get(progress);
+
+ FloatAnimation anim2 =
+ new FloatAnimation(Easing.CUBIC_STANDARD, 0.5f, null, Float.NaN, Float.NaN);
+ anim2.setInitialValue(0f);
+ anim2.setTargetValue(1f);
+ float tweenRadius = anim2.get(progress);
+
+ int startColor = ColorUtils.createColor(250, 250, 250, 180);
+ int endColor = ColorUtils.createColor(200, 200, 200, 0);
+ int paintedColor = Utils.interpolateColor(startColor, endColor, tween);
+
+ float radius = Math.max(mWidth, mHeight) * tweenRadius;
+ mPaint.setColor(paintedColor);
+ context.applyPaint(mPaint);
+ context.clipRect(0f, 0f, mWidth, mHeight);
+ context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
+ context.restorePaint();
+ context.restore();
+ }
+
+ @Override
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, "RIPPLE_MODIFIER");
+ }
+
+ @NonNull
+ public static String name() {
+ return "RippleModifier";
+ }
+
+ public static void apply(@NonNull WireBuffer buffer) {
+ buffer.start(OP_CODE);
+ }
+
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ operations.add(new RippleModifierOperation());
+ }
+
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", OP_CODE, name())
+ .description(
+ "Ripple modifier. This modifier will do a ripple animation on touch down");
+ }
+
+ @Override
+ public void onTouchDown(
+ RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ locationInWindow[0] = 0f;
+ locationInWindow[1] = 0f;
+ component.getLocationInWindow(locationInWindow);
+ animateRipple(x - locationInWindow[0], y - locationInWindow[1]);
+ context.hapticEffect(3);
+ }
+
+ @Override
+ public void onTouchUp(
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {}
+
+ @Override
+ public void onTouchDrag(
+ RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+
+ @Override
+ public void onTouchCancel(
+ RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+}
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 681501d9cdf9..4c1f04ebd3d4 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
@@ -26,6 +26,7 @@ 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.operations.DrawBase4;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
@@ -57,6 +58,11 @@ public class RoundedClipRectModifierOperation extends DrawBase4
return OP_CODE;
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -67,6 +73,11 @@ public class RoundedClipRectModifierOperation extends DrawBase4
apply(buffer, v1, v2, v3, v4);
}
+ /**
+ * 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("Modifier Operations", id(), "RoundedClipRectModifierOperation")
.description("clip with rectangle")
@@ -107,7 +118,8 @@ public class RoundedClipRectModifierOperation extends DrawBase4
}
@Override
- public void layout(@NonNull RemoteContext context, float width, float height) {
+ public void layout(
+ @NonNull RemoteContext context, Component component, float width, float height) {
this.mWidth = width;
this.mHeight = height;
}
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 0b6632057bd2..a5f79ee7e7b7 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
@@ -24,18 +24,24 @@ import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
import com.android.internal.widget.remotecompose.core.operations.Utils;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ListActionsOperation;
import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
/** Represents a scroll modifier. */
-public class ScrollModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+public class ScrollModifierOperation extends ListActionsOperation
+ implements TouchHandler, DecoratorComponent, ScrollDelegate, VariableSupport {
private static final int OP_CODE = Operations.MODIFIER_SCROLL;
public static final String CLASS_NAME = "ScrollModifierOperation";
@@ -43,9 +49,6 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
private final float mMax;
private final float mNotchMax;
- float mWidth = 0;
- float mHeight = 0;
-
int mDirection;
float mTouchDownX;
@@ -63,13 +66,44 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
float mHostDimension;
float mContentDimension;
+ private TouchExpression mTouchExpression;
+
public ScrollModifierOperation(int direction, float position, float max, float notchMax) {
+ super("SCROLL_MODIFIER");
this.mDirection = direction;
this.mPositionExpression = position;
this.mMax = max;
this.mNotchMax = notchMax;
}
+ /**
+ * Inflate the operation
+ *
+ * @param component
+ */
+ public void inflate(Component component) {
+ for (Operation op : mList) {
+ if (op instanceof TouchExpression) {
+ mTouchExpression = (TouchExpression) op;
+ mTouchExpression.setComponent(component);
+ }
+ }
+ }
+
+ @Override
+ public void registerListening(@NonNull RemoteContext context) {
+ if (mTouchExpression != null) {
+ mTouchExpression.registerListening(context);
+ }
+ }
+
+ @Override
+ public void updateVariables(@NonNull RemoteContext context) {
+ if (mTouchExpression != null) {
+ mTouchExpression.updateVariables(context);
+ }
+ }
+
public boolean isVerticalScroll() {
return mDirection == 0;
}
@@ -113,6 +147,12 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
@Override
public void paint(PaintContext context) {
+ for (Operation op : mList) {
+ op.apply(context.getContext());
+ }
+ if (mTouchExpression == null) {
+ return;
+ }
float position =
context.getContext()
.mRemoteComposeState
@@ -130,6 +170,12 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
return "ScrollModifierOperation(" + mDirection + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return CLASS_NAME;
}
@@ -167,7 +213,7 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
}
@Override
- public void layout(RemoteContext context, float width, float height) {
+ public void layout(RemoteContext context, Component component, float width, float height) {
mWidth = width;
mHeight = height;
if (mDirection == 0) { // VERTICAL
@@ -186,18 +232,36 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
mTouchDownY = y;
mInitialScrollX = mScrollX;
mInitialScrollY = mScrollY;
+ if (mTouchExpression != null) {
+ mTouchExpression.updateVariables(context);
+ mTouchExpression.touchDown(context, x + mScrollX, y + mScrollY);
+ }
document.appliedTouchOperation(component);
}
@Override
public void onTouchUp(
- RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ RemoteContext context,
+ CoreDocument document,
+ Component component,
+ float x,
+ float y,
+ float dx,
+ float dy) {
+ if (mTouchExpression != null) {
+ mTouchExpression.updateVariables(context);
+ mTouchExpression.touchUp(context, x + mScrollX, y + mScrollY, dx, dy);
+ }
// If not using touch expression, should add velocity decay here
}
@Override
public void onTouchDrag(
RemoteContext context, CoreDocument document, Component component, float x, float y) {
+ if (mTouchExpression != null) {
+ mTouchExpression.updateVariables(context);
+ mTouchExpression.touchDrag(context, x + mScrollX, y + mScrollY);
+ }
float dx = x - mTouchDownX;
float dy = y - mTouchDownY;
@@ -229,4 +293,35 @@ public class ScrollModifierOperation extends DecoratorModifierOperation implemen
public float getContentDimension() {
return mContentDimension;
}
+
+ @Override
+ public float getScrollX(float currentValue) {
+ if (mDirection == 1) {
+ return mScrollX;
+ }
+ return 0f;
+ }
+
+ @Override
+ public float getScrollY(float currentValue) {
+ if (mDirection == 0) {
+ return mScrollY;
+ }
+ return 0f;
+ }
+
+ @Override
+ public boolean handlesHorizontalScroll() {
+ return mDirection == 1;
+ }
+
+ @Override
+ public boolean handlesVerticalScroll() {
+ return mDirection == 0;
+ }
+
+ @Override
+ public void reset() {
+ // nothing here for now
+ }
}
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 b96d3cc4bbc0..b6977a035c9e 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
@@ -73,7 +73,6 @@ public class ValueFloatChangeActionOperation extends Operation implements Action
@Override
public void runAction(
RemoteContext context, CoreDocument document, Component component, float x, float y) {
- System.out.println("OVERRIDE " + mTargetValueId + " TO " + mValue);
context.overrideFloat(mTargetValueId, 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 d81b7ffd1ef9..766271a70ce4 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
@@ -101,6 +101,11 @@ public class ValueFloatExpressionChangeActionOperation extends Operation
operations.add(new ValueFloatExpressionChangeActionOperation(valueId, value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
.description(
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 fb13b42dbd21..60166a7b2102 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
@@ -99,6 +99,11 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti
operations.add(new ValueIntegerChangeActionOperation(valueId, value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation")
.description(
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 0fe88ad165a9..502508058465 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
@@ -101,6 +101,11 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation
operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
.description(
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 a8d3b87f04b4..8093bb3c64ec 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
@@ -103,6 +103,11 @@ public class ValueStringChangeActionOperation extends Operation implements Actio
operations.add(new ValueStringChangeActionOperation(valueId, value));
}
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation")
.description(
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 f6d743f599d3..05305988a49f 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
@@ -32,6 +32,11 @@ public class WidthModifierOperation extends DimensionModifierOperation {
private static final int OP_CODE = Operations.MODIFIER_WIDTH;
public static final String CLASS_NAME = "WidthModifierOperation";
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return CLASS_NAME;
@@ -94,6 +99,11 @@ public class WidthModifierOperation extends DimensionModifierOperation {
return "WIDTH";
}
+ /**
+ * 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("Modifier Operations", OP_CODE, CLASS_NAME)
.description("define the animation")
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 96ed2cda3e10..35de33a9997a 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
@@ -26,6 +26,7 @@ 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.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
import java.util.List;
@@ -45,7 +46,7 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
return mCurrentValue;
}
- public void setmValue(float value) {
+ public void setValue(float value) {
this.mValue = value;
}
@@ -79,6 +80,12 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
return "ZIndexModifierOperation(" + mValue + ")";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
public static String name() {
return CLASS_NAME;
}
@@ -109,5 +116,5 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
}
@Override
- public void layout(RemoteContext context, float width, float height) {}
+ public void layout(RemoteContext context, Component component, float width, float height) {}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index a56874781e4a..7e467012536d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -20,58 +20,147 @@ import android.annotation.Nullable;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.MonotonicSpline;
+import java.util.Random;
+
/** high performance floating point expression evaluator used in animation */
public class AnimatedFloatExpression {
@NonNull static IntMap<String> sNames = new IntMap<>();
+
+ /** The START POINT in the float NaN space for operators */
public static final int OFFSET = 0x310_000;
+
+ /** ADD operator */
public static final float ADD = asNan(OFFSET + 1);
+
+ /** SUB operator */
public static final float SUB = asNan(OFFSET + 2);
+
+ /** MUL operator */
public static final float MUL = asNan(OFFSET + 3);
+
+ /** DIV operator */
public static final float DIV = asNan(OFFSET + 4);
+
+ /** MOD operator */
public static final float MOD = asNan(OFFSET + 5);
+
+ /** MIN operator */
public static final float MIN = asNan(OFFSET + 6);
+
+ /** MAX operator */
public static final float MAX = asNan(OFFSET + 7);
+
+ /** POW operator */
public static final float POW = asNan(OFFSET + 8);
+
+ /** SQRT operator */
public static final float SQRT = asNan(OFFSET + 9);
+
+ /** ABS operator */
public static final float ABS = asNan(OFFSET + 10);
+
+ /** SIGN operator */
public static final float SIGN = asNan(OFFSET + 11);
+
+ /** COPY_SIGN operator */
public static final float COPY_SIGN = asNan(OFFSET + 12);
+
+ /** EXP operator */
public static final float EXP = asNan(OFFSET + 13);
+
+ /** FLOOR operator */
public static final float FLOOR = asNan(OFFSET + 14);
+
+ /** LOG operator */
public static final float LOG = asNan(OFFSET + 15);
+
+ /** LN operator */
public static final float LN = asNan(OFFSET + 16);
+
+ /** ROUND operator */
public static final float ROUND = asNan(OFFSET + 17);
+
+ /** SIN operator */
public static final float SIN = asNan(OFFSET + 18);
+
+ /** COS operator */
public static final float COS = asNan(OFFSET + 19);
+
+ /** TAN operator */
public static final float TAN = asNan(OFFSET + 20);
+
+ /** ASIN operator */
public static final float ASIN = asNan(OFFSET + 21);
+
+ /** ACOS operator */
public static final float ACOS = asNan(OFFSET + 22);
+ /** ATAN operator */
public static final float ATAN = asNan(OFFSET + 23);
+ /** ATAN2 operator */
public static final float ATAN2 = asNan(OFFSET + 24);
+
+ /** MAD operator */
public static final float MAD = asNan(OFFSET + 25);
+
+ /** IFELSE operator */
public static final float IFELSE = asNan(OFFSET + 26);
+ /** CLAMP operator */
public static final float CLAMP = asNan(OFFSET + 27);
+
+ /** CBRT operator */
public static final float CBRT = asNan(OFFSET + 28);
+
+ /** DEG operator */
public static final float DEG = asNan(OFFSET + 29);
+
+ /** RAD operator */
public static final float RAD = asNan(OFFSET + 30);
+
+ /** CEIL operator */
public static final float CEIL = asNan(OFFSET + 31);
// Array ops
+ /** A DEREF operator */
public static final float A_DEREF = asNan(OFFSET + 32);
+
+ /** Array MAX operator */
public static final float A_MAX = asNan(OFFSET + 33);
+
+ /** Array MIN operator */
public static final float A_MIN = asNan(OFFSET + 34);
+
+ /** A_SUM operator */
public static final float A_SUM = asNan(OFFSET + 35);
+
+ /** A_AVG operator */
public static final float A_AVG = asNan(OFFSET + 36);
+
+ /** A_LEN operator */
public static final float A_LEN = asNan(OFFSET + 37);
+
+ /** A_SPLINE operator */
public static final float A_SPLINE = asNan(OFFSET + 38);
- public static final int LAST_OP = OFFSET + 38;
+ /** RAND Random number 0..1 */
+ public static final float RAND = asNan(OFFSET + 39);
+
+ /** RAND_SEED operator */
+ public static final float RAND_SEED = asNan(OFFSET + 40);
+
+ /** LAST valid operator */
+ public static final int LAST_OP = OFFSET + 40;
- public static final float VAR1 = asNan(OFFSET + 39);
- public static final float VAR2 = asNan(OFFSET + 40);
+ /** VAR1 operator */
+ public static final float VAR1 = asNan(OFFSET + 41);
+
+ /** VAR2 operator */
+ public static final float VAR2 = asNan(OFFSET + 42);
+
+ /** VAR2 operator */
+ public static final float VAR3 = asNan(OFFSET + 43);
// TODO CLAMP, CBRT, DEG, RAD, EXPM1, CEIL, FLOOR
// private static final float FP_PI = (float) Math.PI;
@@ -83,6 +172,7 @@ public class AnimatedFloatExpression {
@NonNull float[] mVar = new float[0];
@Nullable CollectionsAccess mCollectionsAccess;
IntMap<MonotonicSpline> mSplineMap = new IntMap<>();
+ private Random mRandom;
private float getSplineValue(int arrayId, float pos) {
MonotonicSpline fit = mSplineMap.get(arrayId);
@@ -151,10 +241,11 @@ public class AnimatedFloatExpression {
/**
* Evaluate a float expression
*
- * @param ca
- * @param exp
- * @param var
- * @return
+ * @param ca Access to float array collections
+ * @param exp the expressions
+ * @param len the length of the expression array
+ * @param var variables if the expression contains VAR tags
+ * @return the value the expression evaluated to
*/
public float eval(
@NonNull CollectionsAccess ca, @NonNull float[] exp, int len, @NonNull float... var) {
@@ -183,9 +274,10 @@ public class AnimatedFloatExpression {
/**
* Evaluate a float expression
*
- * @param ca
- * @param exp
- * @return
+ * @param ca The access to float arrays
+ * @param exp the expression
+ * @param len the length of the expression sections
+ * @return the value the expression evaluated to
*/
public float eval(@NonNull CollectionsAccess ca, @NonNull float[] exp, int len) {
System.arraycopy(exp, 0, mLocalStack, 0, len);
@@ -304,6 +396,8 @@ public class AnimatedFloatExpression {
sNames.put(k++, "A_AVG");
sNames.put(k++, "A_LEN");
sNames.put(k++, "A_SPLINE");
+ sNames.put(k++, "RAND");
+ sNames.put(k++, "RAND_SEED");
sNames.put(k++, "a[0]");
sNames.put(k++, "a[1]");
@@ -518,9 +612,12 @@ public class AnimatedFloatExpression {
private static final int OP_A_AVG = OFFSET + 36;
private static final int OP_A_LEN = OFFSET + 37;
private static final int OP_A_SPLINE = OFFSET + 38;
- private static final int OP_FIRST_VAR = OFFSET + 39;
- private static final int OP_SECOND_VAR = OFFSET + 40;
- private static final int OP_THIRD_VAR = OFFSET + 41;
+ private static final int OP_RAND = OFFSET + 39;
+ private static final int OP_RAND_SEED = OFFSET + 40;
+
+ private static final int OP_FIRST_VAR = OFFSET + 41;
+ private static final int OP_SECOND_VAR = OFFSET + 42;
+ private static final int OP_THIRD_VAR = OFFSET + 43;
int opEval(int sp, int id) {
float[] array;
@@ -708,6 +805,26 @@ public class AnimatedFloatExpression {
mStack[sp - 1] = getSplineValue(id, mStack[sp]);
return sp - 1;
+ case OP_RAND:
+ if (mRandom == null) {
+ mRandom = new Random();
+ }
+ mStack[sp + 1] = mRandom.nextFloat();
+ return sp + 1;
+
+ case OP_RAND_SEED:
+ float seed = mStack[sp];
+ if (seed == 0) {
+ mRandom = new Random();
+ } else {
+ if (mRandom == null) {
+ mRandom = new Random(Float.floatToRawIntBits(seed));
+ } else {
+ mRandom.setSeed(Float.floatToRawIntBits(seed));
+ }
+ }
+ return sp - 1;
+
case OP_FIRST_VAR:
mStack[sp] = mVar[0];
return sp;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
index 182d36a5eb06..69de5354a923 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
@@ -23,17 +23,45 @@ import android.annotation.Nullable;
* FloatArrayAccess, ListAccess, MapAccess
*/
public interface ArrayAccess {
+ /**
+ * Get a value as a float for an index
+ *
+ * @param index position in the collection
+ * @return
+ */
float getFloatValue(int index);
+ /**
+ * If the objects have id's return the id
+ *
+ * @param index index of the object
+ * @return id or -1 if no id is available
+ */
default int getId(int index) {
return 0;
}
+ /**
+ * Get the backing array of float if available for float arrays
+ *
+ * @return
+ */
@Nullable
float[] getFloats();
+ /**
+ * Get the length of the collection
+ *
+ * @return length of the collection
+ */
int getLength();
+ /**
+ * Get the value as an integer if available
+ *
+ * @param index the position in the collection
+ * @return it value as and integer
+ */
default int getIntValue(int index) {
return (int) getFloatValue(index);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
new file mode 100644
index 000000000000..cd8b7b865cd2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+
+/** A Modifier that provides semantic info. */
+public interface AccessibilityModifier extends ModifierOperation, AccessibleComponent {
+ int getOpCode();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
new file mode 100644
index 000000000000..291ad477703b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+/** Marker interface for a Component or Modifier that is relevant for Semantics. */
+public interface AccessibilitySemantics {
+
+ /**
+ * Determines if this element is interesting for semantic analysis.
+ *
+ * <p>This method is used to filter elements during semantic analysis. By default, all elements
+ * are considered interesting. Subclasses can override this method to exclude specific elements
+ * from semantic analysis.
+ *
+ * @return {@code true} if this element is interesting for semantic analysis, {@code false}
+ * otherwise.
+ */
+ default boolean isInterestingForSemantics() {
+ return true;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
new file mode 100644
index 000000000000..e07fc4d9a8f8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+import android.annotation.Nullable;
+
+public interface AccessibleComponent extends AccessibilitySemantics {
+ default @Nullable Integer getContentDescriptionId() {
+ return null;
+ }
+
+ default @Nullable Integer getTextId() {
+ return null;
+ }
+
+ default @Nullable Role getRole() {
+ return null;
+ }
+
+ default boolean isClickable() {
+ return false;
+ }
+
+ default CoreSemantics.Mode getMode() {
+ return CoreSemantics.Mode.SET;
+ }
+
+ // Our master list
+ // https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role
+ enum Role {
+ BUTTON("Button"),
+ CHECKBOX("Checkbox"),
+ SWITCH("Switch"),
+ RADIO_BUTTON("RadioButton"),
+ TAB("Tab"),
+ IMAGE("Image"),
+ DROPDOWN_LIST("DropdownList"),
+ PICKER("Picker"),
+ CAROUSEL("Carousel"),
+ UNKNOWN(null);
+
+ @Nullable private final String mDescription;
+
+ Role(@Nullable String description) {
+ this.mDescription = description;
+ }
+
+ @Nullable
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public static Role fromInt(int i) {
+ if (i < UNKNOWN.ordinal()) {
+ return Role.values()[i];
+ }
+ return Role.UNKNOWN;
+ }
+ }
+}
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
new file mode 100644
index 000000000000..4047dd27d163
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -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.internal.widget.remotecompose.core.semantics;
+
+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.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Implementation of the most common semantics used in typical Android apps. */
+public class CoreSemantics extends Operation implements AccessibilityModifier {
+ public int mContentDescriptionId = 0;
+ public @Nullable Role mRole = null;
+ public int mTextId = 0;
+ public int mStateDescriptionId = 0;
+ public boolean mEnabled = true;
+ public Mode mMode = Mode.SET;
+ public boolean mClickable = false;
+
+ @Override
+ public int getOpCode() {
+ return Operations.ACCESSIBILITY_SEMANTICS;
+ }
+
+ @Nullable
+ @Override
+ public Role getRole() {
+ return mRole;
+ }
+
+ @Override
+ public Mode getMode() {
+ return mMode;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ buffer.writeInt(mContentDescriptionId);
+ buffer.writeByte((mRole != null) ? mRole.ordinal() : -1);
+ buffer.writeInt(mTextId);
+ buffer.writeInt(mStateDescriptionId);
+ buffer.writeByte(mMode.ordinal());
+ buffer.writeBoolean(mEnabled);
+ buffer.writeBoolean(mClickable);
+ }
+
+ private void read(WireBuffer buffer) {
+ mContentDescriptionId = buffer.readInt();
+ mRole = Role.fromInt(buffer.readByte());
+ mTextId = buffer.readInt();
+ mStateDescriptionId = buffer.readInt();
+ mMode = Mode.values()[buffer.readByte()];
+ mEnabled = buffer.readBoolean();
+ mClickable = buffer.readBoolean();
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // Handled via touch helper
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("SEMANTICS");
+ if (mMode != Mode.SET) {
+ builder.append(" ");
+ builder.append(mMode);
+ }
+ if (mRole != null) {
+ builder.append(" ");
+ builder.append(mRole);
+ }
+ if (mContentDescriptionId > 0) {
+ builder.append(" contentDescription=");
+ builder.append(mContentDescriptionId);
+ }
+ if (mTextId > 0) {
+ builder.append(" text=");
+ builder.append(mTextId);
+ }
+ if (mStateDescriptionId > 0) {
+ builder.append(" stateDescription=");
+ builder.append(mStateDescriptionId);
+ }
+ if (!mEnabled) {
+ builder.append(" disabled");
+ }
+ if (mClickable) {
+ builder.append(" clickable");
+ }
+ return builder.toString();
+ }
+
+ @Nullable
+ @Override
+ public String deepToString(String indent) {
+ return indent + this;
+ }
+
+ @NonNull
+ public String serializedName() {
+ return "SEMANTICS";
+ }
+
+ @Override
+ public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+ serializer.append(indent, serializedName() + " = " + this);
+ }
+
+ public static void read(WireBuffer buffer, List<Operation> operations) {
+ CoreSemantics semantics = new CoreSemantics();
+
+ semantics.read(buffer);
+
+ operations.add(semantics);
+ }
+
+ @Override
+ public Integer getContentDescriptionId() {
+ return mContentDescriptionId != 0 ? mContentDescriptionId : null;
+ }
+
+ public @Nullable Integer getStateDescriptionId() {
+ return mStateDescriptionId != 0 ? mStateDescriptionId : null;
+ }
+
+ public @Nullable Integer getTextId() {
+ return mTextId != 0 ? mTextId : null;
+ }
+
+ public enum Mode {
+ SET,
+ CLEAR_AND_SET,
+ MERGE
+ }
+}
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 975213f76bd2..2c874b183a62 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
@@ -68,6 +68,11 @@ public class BooleanConstant extends Operation {
return "BooleanConstant[" + mId + "] = " + mValue + "";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "OrigamiBoolean";
@@ -108,6 +113,11 @@ public class BooleanConstant extends Operation {
operations.add(new BooleanConstant(id, value));
}
+ /**
+ * 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("Expressions Operations", OP_CODE, "BooleanConstant")
.description("A boolean and its associated id")
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 210a15ac7ca4..5462d3e069ed 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
@@ -60,6 +60,11 @@ public class IntegerConstant extends Operation {
return "IntegerConstant[" + mId + "] = " + mValue + "";
}
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
@NonNull
public static String name() {
return "IntegerConstant";
@@ -100,6 +105,11 @@ public class IntegerConstant extends Operation {
operations.add(new IntegerConstant(id, value));
}
+ /**
+ * 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("Expressions Operations", id(), "IntegerConstant")
.description("A integer and its associated id")
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 9875c935c112..1a3cdb1a96d7 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
@@ -96,6 +96,11 @@ public class LongConstant extends Operation {
operations.add(new LongConstant(id, value));
}
+ /**
+ * 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("Expressions Operations", OP_CODE, "LongConstant")
.description("A boolean and its associated id")
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 19b4b36b504c..7dad2931c97f 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -32,6 +32,7 @@ import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
+import com.android.internal.widget.remotecompose.accessibility.RemoteComposeTouchHelper;
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
@@ -61,6 +62,15 @@ public class RemoteComposePlayer extends FrameLayout {
}
/**
+ * Returns true if the document supports drag touch events
+ *
+ * @return true if draggable content, false otherwise
+ */
+ public boolean isDraggable() {
+ return mInner.isDraggable();
+ }
+
+ /**
* Turn on debug information
*
* @param debugFlags 1 to set debug on
@@ -83,8 +93,12 @@ public class RemoteComposePlayer extends FrameLayout {
} else {
Log.e("RemoteComposePlayer", "Unsupported document ");
}
+
+ RemoteComposeTouchHelper.REGISTRAR.setAccessibilityDelegate(this, value.getDocument());
} else {
mInner.setDocument(null);
+
+ RemoteComposeTouchHelper.REGISTRAR.clearAccessibilityDelegate(this);
}
mapColors();
setupSensors();
@@ -96,6 +110,7 @@ public class RemoteComposePlayer extends FrameLayout {
provideHapticFeedback(type);
}
});
+ mInner.checkShaders(mShaderControl);
}
/**
@@ -235,22 +250,23 @@ public class RemoteComposePlayer extends FrameLayout {
mInner.clearLocalString("SYSTEM:" + name);
}
- public interface ClickCallbacks {
- void click(int id, String metadata);
+ /** Id action callback interface */
+ public interface IdActionCallbacks {
+ void onAction(int id, String metadata);
}
/**
- * Add a callback for handling click events on the document
+ * Add a callback for handling id actions events on the document
*
- * @param callback the callback lambda that will be used when a click is detected
+ * @param callback the callback lambda that will be used when a action is executed
* <p>The parameter of the callback are:
* <ul>
- * <li>id : the id of the clicked area
- * <li>metadata: a client provided unstructured string associated with that area
+ * <li>id : the id of the action
+ * <li>metadata: a client provided unstructured string associated with that id action
* </ul>
*/
- public void addClickListener(ClickCallbacks callback) {
- mInner.addClickListener((id, metadata) -> callback.click(id, metadata));
+ public void addIdActionListener(IdActionCallbacks callback) {
+ mInner.addIdActionListener((id, metadata) -> callback.onAction(id, metadata));
}
/**
@@ -669,4 +685,19 @@ public class RemoteComposePlayer extends FrameLayout {
public float getEvalTime() {
return mInner.getEvalTime();
}
+
+ private CoreDocument.ShaderControl mShaderControl =
+ (shader) -> {
+ return false;
+ };
+
+ /**
+ * Sets the controller for shaders. Note set before loading the document. The default is to not
+ * accept shaders.
+ *
+ * @param ctl the controller
+ */
+ public void setShaderControl(CoreDocument.ShaderControl ctl) {
+ mShaderControl = ctl;
+ }
}
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 bc7d5e108e1d..0712ea496b57 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
@@ -250,7 +250,7 @@ public class AndroidPaintContext extends PaintContext {
@Override
public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) {
String str = getText(textId);
- if (end == -1) {
+ if (end == -1 || end > str.length()) {
end = str.length();
}
@@ -505,6 +505,9 @@ public class AndroidPaintContext extends PaintContext {
return;
}
ShaderData data = getShaderData(shaderId);
+ if (data == null) {
+ return;
+ }
RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId()));
String[] names = data.getUniformFloatNames();
for (int i = 0; i < names.length; i++) {
@@ -757,11 +760,17 @@ public class AndroidPaintContext extends PaintContext {
private Path getPath(int id, float start, float end) {
AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ Path p = (Path) androidContext.mRemoteComposeState.getPath(id);
+ if (p != null) {
+ return p;
+ }
Path path = new Path();
float[] pathData = androidContext.mRemoteComposeState.getPathData(id);
if (pathData != null) {
FloatsToPath.genPath(path, pathData, start, end);
+ androidContext.mRemoteComposeState.putPath(id, path);
}
+
return path;
}
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 ecfd13aa66b6..c7b1166e113e 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
@@ -205,19 +205,46 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
return count;
}
+ /**
+ * set a float externally
+ *
+ * @param id
+ * @param value
+ */
public void setExternalFloat(int id, float value) {
mARContext.loadFloat(id, value);
}
+ /**
+ * Returns true if the document supports drag touch events
+ *
+ * @return true if draggable content, false otherwise
+ */
+ public boolean isDraggable() {
+ if (mDocument == null) {
+ return false;
+ }
+ return mDocument.getDocument().hasTouchListener();
+ }
+
+ /**
+ * Check shaders and disable them
+ *
+ * @param shaderControl the callback to validate the shader
+ */
+ public void checkShaders(CoreDocument.ShaderControl shaderControl) {
+ mDocument.getDocument().checkShaders(mARContext, shaderControl);
+ }
+
public interface ClickCallbacks {
void click(int id, String metadata);
}
- public void addClickListener(ClickCallbacks callback) {
+ public void addIdActionListener(ClickCallbacks callback) {
if (mDocument == null) {
return;
}
- mDocument.getDocument().addClickListener((id, metadata) -> callback.click(id, metadata));
+ mDocument.getDocument().addIdActionListener((id, metadata) -> callback.click(id, metadata));
}
public int getTheme() {
@@ -241,9 +268,9 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
case MotionEvent.ACTION_DOWN:
mActionDownPoint.x = (int) event.getX();
mActionDownPoint.y = (int) event.getY();
- mInActionDown = true;
CoreDocument doc = mDocument.getDocument();
if (doc.hasTouchListener()) {
+ mInActionDown = true;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
@@ -251,8 +278,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
}
mVelocityTracker.addMovement(event);
doc.touchDown(mARContext, event.getX(), event.getY());
+ invalidate();
+ return true;
}
- return true;
+ return false;
case MotionEvent.ACTION_CANCEL:
mInActionDown = false;
@@ -262,8 +291,11 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
float dx = mVelocityTracker.getXVelocity(pointerId);
float dy = mVelocityTracker.getYVelocity(pointerId);
doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy);
+ invalidate();
+ return true;
}
- return true;
+ return false;
+
case MotionEvent.ACTION_UP:
mInActionDown = false;
performClick();
@@ -273,8 +305,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
float dx = mVelocityTracker.getXVelocity(pointerId);
float dy = mVelocityTracker.getYVelocity(pointerId);
doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy);
+ invalidate();
+ return true;
}
- return true;
+ return false;
case MotionEvent.ACTION_MOVE:
if (mInActionDown) {
@@ -286,7 +320,9 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
invalidate();
}
}
+ return true;
}
+ return false;
}
return false;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5c03c5cca66d..8e3303a4ddbd 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -92,6 +92,7 @@ cc_library_shared_for_libandroid_runtime {
"android_view_VelocityTracker.cpp",
"android_view_VerifiedKeyEvent.cpp",
"android_view_VerifiedMotionEvent.cpp",
+ "com_android_internal_util_ArrayUtils.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"core_jni_helpers.cpp",
":deviceproductinfoconstants_aidl",
@@ -214,11 +215,13 @@ cc_library_shared_for_libandroid_runtime {
"android_media_ToneGenerator.cpp",
"android_hardware_Camera.cpp",
"android_hardware_camera2_CameraMetadata.cpp",
+ "android_hardware_camera2_CameraDevice.cpp",
"android_hardware_camera2_DngCreator.cpp",
"android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp",
"android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
+ "android_hardware_display_DisplayTopology.cpp",
"android_hardware_HardwareBuffer.cpp",
"android_hardware_OverlayProperties.cpp",
"android_hardware_SensorManager.cpp",
@@ -278,6 +281,7 @@ cc_library_shared_for_libandroid_runtime {
"libasync_safe",
"libbinderthreadstateutils",
"libdmabufinfo",
+ "libgenfslabelsversion.ffi",
"libgui_window_info_static",
"libkernelconfigs",
"libnativehelper_lazy",
@@ -304,7 +308,12 @@ cc_library_shared_for_libandroid_runtime {
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
"camera_platform_flags_c_lib",
+ "android.hardware.common.fmq-V1-cpp",
+ "android.hardware.common-V2-cpp",
+ "android.hardware.common.fmq-V1-ndk",
+ "android.hardware.common-V2-ndk",
"libandroid_net",
+ "libfmq",
"libbattery",
"libnetdutils",
"libmemtrack",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 00a62977de43..78d69f0714e0 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -77,6 +77,7 @@ extern int register_android_opengl_jni_GLES32(JNIEnv* env);
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
+extern int register_android_hardware_camera2_CameraDevice(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env);
extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
@@ -219,6 +220,7 @@ extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_security_VerityUtils(JNIEnv* env);
+extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
extern int register_android_window_WindowInfosListener(JNIEnv* env);
extern int register_android_window_ScreenCapture(JNIEnv* env);
@@ -1620,9 +1622,11 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_com_android_internal_security_VerityUtils),
+ REG_JNI(register_com_android_internal_util_ArrayUtils),
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
+ REG_JNI(register_android_hardware_camera2_CameraDevice),
REG_JNI(register_android_hardware_camera2_DngCreator),
REG_JNI(register_android_hardware_camera2_impl_CameraExtensionJpegProcessor),
REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp
new file mode 100644
index 000000000000..493c7073416c
--- /dev/null
+++ b/core/jni/android_hardware_camera2_CameraDevice.cpp
@@ -0,0 +1,148 @@
+/*
+**
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+// #define LOG_NDEBUG 0
+
+#define ATRACE_TAG ATRACE_TAG_CAMERA
+
+#include <memory>
+#define LOG_TAG "CameraDevice-JNI"
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+#include <vector>
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include "android_os_Parcel.h"
+#include "core_jni_helpers.h"
+#include <android/binder_parcel_jni.h>
+#include <android/hardware/camera2/ICameraDeviceUser.h>
+#include <aidl/android/hardware/common/fmq/MQDescriptor.h>
+#include <aidl/android/hardware/common/fmq/SynchronizedReadWrite.h>
+#include <fmq/AidlMessageQueue.h>
+#include <camera/CameraMetadata.h>
+
+using namespace android;
+
+using ::android::AidlMessageQueue;
+using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+class FMQReader {
+ public:
+ FMQReader(MQDescriptor<int8_t, SynchronizedReadWrite> &resultMQ) {
+ mCaptureResultMetadataQueue = std::make_shared<ResultMetadataQueue>(resultMQ);
+ }
+ std::shared_ptr<CameraMetadata> readOneResultMetadata(long metadataSize);
+ private:
+ std::shared_ptr<ResultMetadataQueue> mCaptureResultMetadataQueue = nullptr;
+};
+
+std::shared_ptr<CameraMetadata> FMQReader::readOneResultMetadata(long metadataSize) {
+ ATRACE_CALL();
+ if (metadataSize == 0) {
+ return nullptr;
+ }
+ auto metadataVec = std::make_unique<int8_t []>(metadataSize);
+ bool read = mCaptureResultMetadataQueue->read(metadataVec.get(), metadataSize);
+ if (!read) {
+ ALOGE("%s capture metadata could't be read from fmq", __FUNCTION__);
+ return nullptr;
+ }
+
+ // Takes ownership of metadataVec, this doesn't copy
+ std::shared_ptr<CameraMetadata> retVal =
+ std::make_shared<CameraMetadata>(
+ reinterpret_cast<camera_metadata_t *>(metadataVec.release()));
+ return retVal;
+}
+
+extern "C" {
+
+static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz,
+ jobject resultParcel) {
+ AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel);
+ if (resultAParcel == nullptr) {
+ ALOGE("%s: Error creating result parcel", __FUNCTION__);
+ return 0;
+ }
+ AParcel_setDataPosition(resultAParcel, 0);
+
+ MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ;
+ if (resultMQ.readFromParcel(resultAParcel) != OK) {
+ ALOGE("%s: read from result parcel failed", __FUNCTION__);
+ return 0;
+ }
+ return reinterpret_cast<jlong>(new std::shared_ptr<FMQReader>(
+ new FMQReader(resultMQ)));
+}
+
+static std::shared_ptr<FMQReader>* FMQReader_getSharedPtr(jlong fmqReaderLongPtr) {
+ return reinterpret_cast<std::shared_ptr<FMQReader>* >(fmqReaderLongPtr);
+}
+
+static jlong CameraDevice_readResultMetadata(JNIEnv *env, jclass thiz, jlong ptr,
+ jlong metadataSize) {
+ ALOGV("%s", __FUNCTION__);
+
+ FMQReader *fmqReader = FMQReader_getSharedPtr(ptr)->get();
+ auto metadataSp = fmqReader->readOneResultMetadata(metadataSize);
+ auto retVal = new std::shared_ptr<CameraMetadata>(metadataSp);
+ return reinterpret_cast<jlong>(retVal);
+}
+
+static void CameraDevice_close(JNIEnv *env, jclass thiz, jlong ptr) {
+ ALOGV("%s", __FUNCTION__);
+
+ auto fmqPtr = FMQReader_getSharedPtr(ptr);
+ if (fmqPtr != nullptr) {
+ delete fmqPtr;
+ }
+}
+
+}
+
+//-------------------------------------------------
+#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/impl/CameraDeviceImpl"
+static const JNINativeMethod gCameraDeviceMethods[] = {
+// static methods
+ { "nativeCreateFMQReader",
+ "(Landroid/os/Parcel;)J",
+ (void *)CameraDevice_createFMQReader},
+ { "nativeReadResultMetadata",
+ "(JJ)J",
+ (void *)CameraDevice_readResultMetadata},
+ { "nativeClose",
+ "(J)V",
+ (void*)CameraDevice_close},
+// instance methods
+};
+
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_camera2_CameraDevice(JNIEnv *env)
+{
+ // Register native functions
+ return RegisterMethodsOrDie(env,
+ CAMERA_DEVICE_CLASS_NAME,
+ gCameraDeviceMethods,
+ NELEM(gCameraDeviceMethods));
+}
+
+extern "C" {
+
+} // extern "C" \ No newline at end of file
diff --git a/core/jni/android_hardware_display_DisplayTopology.cpp b/core/jni/android_hardware_display_DisplayTopology.cpp
new file mode 100644
index 000000000000..d9e802de81e0
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DisplayTopology-JNI"
+
+#include <android_hardware_display_DisplayTopology.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Errors.h>
+
+#include "jni_wrappers.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+ jclass clazz;
+ jfieldID primaryDisplayId;
+ jfieldID displayNodes;
+} gDisplayTopologyGraphClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID adjacentDisplays;
+} gDisplayTopologyGraphNodeClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID position;
+ jfieldID offsetPx;
+} gDisplayTopologyGraphAdjacentDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+status_t android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(
+ JNIEnv* env, jobject adjacentDisplayObj, DisplayTopologyAdjacentDisplay* adjacentDisplay) {
+ adjacentDisplay->displayId = ui::LogicalDisplayId{
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId)};
+ adjacentDisplay->position = static_cast<DisplayTopologyPosition>(
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position));
+ adjacentDisplay->offsetPx =
+ env->GetFloatField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx);
+ return OK;
+}
+
+status_t android_hardware_display_DisplayTopologyGraphNode_toNative(
+ JNIEnv* env, jobject nodeObj,
+ std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>>&
+ graph) {
+ ui::LogicalDisplayId displayId = ui::LogicalDisplayId{
+ env->GetIntField(nodeObj, gDisplayTopologyGraphNodeClassInfo.displayId)};
+
+ jobjectArray adjacentDisplaysArray = static_cast<jobjectArray>(
+ env->GetObjectField(nodeObj, gDisplayTopologyGraphNodeClassInfo.adjacentDisplays));
+
+ if (adjacentDisplaysArray) {
+ jsize length = env->GetArrayLength(adjacentDisplaysArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject>
+ adjacentDisplayObj(env, env->GetObjectArrayElement(adjacentDisplaysArray, i));
+ if (NULL != adjacentDisplayObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ DisplayTopologyAdjacentDisplay adjacentDisplay;
+ android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(env,
+ adjacentDisplayObj
+ .get(),
+ &adjacentDisplay);
+ graph[displayId].push_back(adjacentDisplay);
+ }
+ }
+ return OK;
+}
+
+DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(JNIEnv* env,
+ jobject topologyObj) {
+ DisplayTopologyGraph topology;
+ topology.primaryDisplayId = ui::LogicalDisplayId{
+ env->GetIntField(topologyObj, gDisplayTopologyGraphClassInfo.primaryDisplayId)};
+
+ jobjectArray nodesArray = static_cast<jobjectArray>(
+ env->GetObjectField(topologyObj, gDisplayTopologyGraphClassInfo.displayNodes));
+
+ if (nodesArray) {
+ jsize length = env->GetArrayLength(nodesArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject> nodeObj(env, env->GetObjectArrayElement(nodesArray, i));
+ if (NULL != nodeObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ android_hardware_display_DisplayTopologyGraphNode_toNative(env, nodeObj.get(),
+ topology.graph);
+ }
+ }
+ return topology;
+}
+
+// ----------------------------------------------------------------------------
+
+int register_android_hardware_display_DisplayTopology(JNIEnv* env) {
+ jclass graphClazz = FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph");
+ gDisplayTopologyGraphClassInfo.clazz = MakeGlobalRefOrDie(env, graphClazz);
+
+ gDisplayTopologyGraphClassInfo.primaryDisplayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "primaryDisplayId", "I");
+ gDisplayTopologyGraphClassInfo.displayNodes =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "displayNodes",
+ "[Landroid/hardware/display/DisplayTopologyGraph$DisplayNode;");
+
+ jclass displayNodeClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$DisplayNode");
+ gDisplayTopologyGraphNodeClassInfo.clazz = MakeGlobalRefOrDie(env, displayNodeClazz);
+ gDisplayTopologyGraphNodeClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "displayId", "I");
+ gDisplayTopologyGraphNodeClassInfo.adjacentDisplays =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "adjacentDisplays",
+ "[Landroid/hardware/display/DisplayTopologyGraph$AdjacentDisplay;");
+
+ jclass adjacentDisplayClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$AdjacentDisplay");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz =
+ MakeGlobalRefOrDie(env, adjacentDisplayClazz);
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "displayId",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "position",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "offsetPx",
+ "F");
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_hardware_display_DisplayTopology.h b/core/jni/android_hardware_display_DisplayTopology.h
new file mode 100644
index 000000000000..390191f827d8
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <input/DisplayTopologyGraph.h>
+
+#include "jni.h"
+
+namespace android {
+
+/**
+ * Copies the contents of a DVM DisplayTopology object to a new native DisplayTopology instance.
+ * Returns DisplayTopology.
+ */
+extern DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(
+ JNIEnv* env, jobject eventObj);
+
+} // namespace android
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 704aef3cd131..4ba1ae9d670d 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -51,6 +51,18 @@
#define ENCODING_DTS_UHD_P2 30
#define ENCODING_DSD 31
#define ENCODING_AC4_L4 32
+#define ENCODING_IAMF_SIMPLE_PROFILE_OPUS 33
+#define ENCODING_IAMF_SIMPLE_PROFILE_AAC 34
+#define ENCODING_IAMF_SIMPLE_PROFILE_FLAC 35
+#define ENCODING_IAMF_SIMPLE_PROFILE_PCM 36
+#define ENCODING_IAMF_BASE_PROFILE_OPUS 37
+#define ENCODING_IAMF_BASE_PROFILE_AAC 38
+#define ENCODING_IAMF_BASE_PROFILE_FLAC 39
+#define ENCODING_IAMF_BASE_PROFILE_PCM 40
+#define ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS 41
+#define ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC 42
+#define ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC 43
+#define ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM 44
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -128,6 +140,30 @@ static inline audio_format_t audioFormatToNative(int audioFormat)
return AUDIO_FORMAT_DTS_UHD_P2;
case ENCODING_DSD:
return AUDIO_FORMAT_DSD;
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ return AUDIO_FORMAT_IAMF_SIMPLE_OPUS;
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ return AUDIO_FORMAT_IAMF_SIMPLE_AAC;
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ return AUDIO_FORMAT_IAMF_SIMPLE_FLAC;
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
+ return AUDIO_FORMAT_IAMF_SIMPLE_PCM;
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ return AUDIO_FORMAT_IAMF_BASE_OPUS;
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ return AUDIO_FORMAT_IAMF_BASE_AAC;
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ return AUDIO_FORMAT_IAMF_BASE_FLAC;
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ return AUDIO_FORMAT_IAMF_BASE_PCM;
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ return AUDIO_FORMAT_IAMF_BASE_ENHANCED_OPUS;
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ return AUDIO_FORMAT_IAMF_BASE_ENHANCED_AAC;
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ return AUDIO_FORMAT_IAMF_BASE_ENHANCED_FLAC;
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ return AUDIO_FORMAT_IAMF_BASE_ENHANCED_PCM;
default:
return AUDIO_FORMAT_INVALID;
}
@@ -211,6 +247,30 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat)
return ENCODING_DEFAULT;
case AUDIO_FORMAT_DSD:
return ENCODING_DSD;
+ case AUDIO_FORMAT_IAMF_SIMPLE_OPUS:
+ return ENCODING_IAMF_SIMPLE_PROFILE_OPUS;
+ case AUDIO_FORMAT_IAMF_SIMPLE_AAC:
+ return ENCODING_IAMF_SIMPLE_PROFILE_AAC;
+ case AUDIO_FORMAT_IAMF_SIMPLE_FLAC:
+ return ENCODING_IAMF_SIMPLE_PROFILE_FLAC;
+ case AUDIO_FORMAT_IAMF_SIMPLE_PCM:
+ return ENCODING_IAMF_SIMPLE_PROFILE_PCM;
+ case AUDIO_FORMAT_IAMF_BASE_OPUS:
+ return ENCODING_IAMF_BASE_PROFILE_OPUS;
+ case AUDIO_FORMAT_IAMF_BASE_AAC:
+ return ENCODING_IAMF_BASE_PROFILE_AAC;
+ case AUDIO_FORMAT_IAMF_BASE_FLAC:
+ return ENCODING_IAMF_BASE_PROFILE_FLAC;
+ case AUDIO_FORMAT_IAMF_BASE_PCM:
+ return ENCODING_IAMF_BASE_PROFILE_PCM;
+ case AUDIO_FORMAT_IAMF_BASE_ENHANCED_OPUS:
+ return ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS;
+ case AUDIO_FORMAT_IAMF_BASE_ENHANCED_AAC:
+ return ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC;
+ case AUDIO_FORMAT_IAMF_BASE_ENHANCED_FLAC:
+ return ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC;
+ case AUDIO_FORMAT_IAMF_BASE_ENHANCED_PCM:
+ return ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM;
default:
return ENCODING_INVALID;
}
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 7a4670f4e49d..805d5ad41e83 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -18,18 +18,19 @@
#include <errno.h>
#include <fcntl.h>
-
+#include <genfslabelsversion.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
#include <utils/Log.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include "jni.h"
+#include <atomic>
+#include <memory>
+
#include "core_jni_helpers.h"
-#include "selinux/selinux.h"
+#include "jni.h"
#include "selinux/android.h"
-#include <memory>
-#include <atomic>
-#include <nativehelper/ScopedLocalRef.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include "selinux/selinux.h"
namespace android {
namespace {
@@ -404,8 +405,19 @@ static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr, jin
}
/*
+ * Function: getGenfsLabelsVersion
+ * Purpose: get which genfs labels version /vendor uses
+ * Returns: int: genfs labels version of /vendor
+ * Exceptions: none
+ */
+static jint getGenfsLabelsVersion(JNIEnv *, jclass) {
+ return get_genfs_labels_version();
+}
+
+/*
* JNI registration.
*/
+// clang-format off
static const JNINativeMethod method_table[] = {
/* name, signature, funcPtr */
{ "checkSELinuxAccess" , "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z" , (void*)checkSELinuxAccess },
@@ -420,7 +432,9 @@ static const JNINativeMethod method_table[] = {
{ "setFileContext" , "(Ljava/lang/String;Ljava/lang/String;)Z" , (void*)setFileCon },
{ "setFSCreateContext" , "(Ljava/lang/String;)Z" , (void*)setFSCreateCon },
{ "fileSelabelLookup" , "(Ljava/lang/String;)Ljava/lang/String;" , (void*)fileSelabelLookup},
+ { "getGenfsLabelsVersion" , "()I" , (void *)getGenfsLabelsVersion},
};
+// clang-format on
static int log_callback(int type, const char *fmt, ...) {
va_list ap;
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index a09c405de1cd..7ff1f8c4a748 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -40,6 +40,7 @@ static struct {
jmethodID dispatchHotplug;
jmethodID dispatchHotplugConnectionError;
jmethodID dispatchModeChanged;
+ jmethodID dispatchModeRejected;
jmethodID dispatchFrameRateOverrides;
jmethodID dispatchHdcpLevelsChanged;
@@ -95,6 +96,7 @@ private:
void dispatchHotplugConnectionError(nsecs_t timestamp, int errorCode) override;
void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
nsecs_t renderPeriod) override;
+ void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override;
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -271,6 +273,18 @@ void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, Physical
mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
}
+void NativeDisplayEventReceiver::dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking Mode Rejected handler.", this);
+ env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeRejected,
+ displayId.value, modeId);
+ ALOGV("receiver %p ~ Returned from Mode Rejected handler.", this);
+ }
+}
+
void NativeDisplayEventReceiver::dispatchFrameRateOverrides(
nsecs_t timestamp, PhysicalDisplayId displayId, std::vector<FrameRateOverride> overrides) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -405,6 +419,9 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
gDisplayEventReceiverClassInfo.dispatchModeChanged =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
"(JJIJ)V");
+ gDisplayEventReceiverClassInfo.dispatchModeRejected =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeRejected",
+ "(JI)V");
gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index b2eeff36c007..f40cfd9f8e51 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -532,7 +532,12 @@ static inline bool app_compat_16kb_enabled() {
static const size_t kPageSize = getpagesize();
// App compat is only applicable on 16kb-page-size devices.
- return kPageSize == 0x4000;
+ if (kPageSize != 0x4000) {
+ return false;
+ }
+
+ // Explicit disabled status for app compat
+ return !android::base::GetBoolProperty("pm.16kb.app_compat.disabled", false);
}
static jint
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 284c2997f9a9..aeaeeca3e845 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -89,6 +89,7 @@
#if defined(__BIONIC__)
extern "C" void android_reset_stack_guards();
+extern "C" void android_set_16kb_appcompat_mode(bool enable_app_compat);
#endif
namespace {
@@ -350,6 +351,7 @@ enum RuntimeFlags : uint32_t {
NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
PROFILEABLE = 1 << 24,
DEBUG_ENABLE_PTRACE = 1 << 25,
+ ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26,
};
enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -2117,6 +2119,12 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
fail_fn);
+ if ((runtime_flags & RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT) != 0) {
+ android_set_16kb_appcompat_mode(true);
+ // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+ // runtime.
+ runtime_flags &= ~RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT;
+ }
__android_log_close();
AStatsSocket_close();
diff --git a/core/jni/com_android_internal_util_ArrayUtils.cpp b/core/jni/com_android_internal_util_ArrayUtils.cpp
new file mode 100644
index 000000000000..c70625815b90
--- /dev/null
+++ b/core/jni/com_android_internal_util_ArrayUtils.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ArrayUtils"
+
+#include <android-base/logging.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <string.h>
+#include <unistd.h>
+#include <utils/Log.h>
+
+namespace android {
+
+static size_t GetCacheLineSize() {
+ long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
+ if (size <= 0) {
+ ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes");
+ return 32;
+ }
+ // The cache line size should always be a power of 2.
+ CHECK((size & (size - 1)) == 0);
+
+ return size;
+}
+
+static void CleanCacheLineContainingAddress(const uint8_t* p) {
+#if defined(__aarch64__)
+ // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency".
+ // It writes the cache line back to the "point-of-coherency", i.e. main memory.
+ asm volatile("dc cvac, %0" ::"r"(p));
+#elif defined(__i386__) || defined(__x86_64__)
+ asm volatile("clflush (%0)" ::"r"(p));
+#elif defined(__riscv)
+ // This should eventually work, but it is not ready to be enabled yet:
+ // 1.) The Android emulator needs to add support for zicbom.
+ // 2.) Kernel needs to enable zicbom in usermode.
+ // 3.) Android clang needs to add zicbom to the target.
+ // asm volatile("cbo.clean (%0)" ::"r"(p));
+#elif defined(__arm__)
+ // arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache.
+ // It is not the same as cacheflush(2) as documented in the Linux man-pages project.
+#else
+#error "Unknown architecture"
+#endif
+}
+
+static void CleanDataCache(const uint8_t* p, size_t buffer_size, size_t cache_line_size) {
+ // Clean the first line that overlaps the buffer.
+ CleanCacheLineContainingAddress(p);
+ // Clean any additional lines that overlap the buffer. Use cache-line-aligned addresses to
+ // ensure that (a) the last cache line gets flushed, and (b) no cache line is flushed twice.
+ for (size_t i = cache_line_size - ((uintptr_t)p & (cache_line_size - 1)); i < buffer_size;
+ i += cache_line_size) {
+ CleanCacheLineContainingAddress(p + i);
+ }
+}
+
+static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) {
+ static const size_t cache_line_size = GetCacheLineSize();
+
+ if (array == nullptr) {
+ return;
+ }
+
+ size_t buffer_size = env->GetArrayLength(array) * component_len;
+ if (buffer_size == 0) {
+ return;
+ }
+
+ // ART guarantees that GetPrimitiveArrayCritical never copies.
+ jboolean isCopy;
+ void* elems = env->GetPrimitiveArrayCritical(array, &isCopy);
+ CHECK(!isCopy);
+
+#ifdef __BIONIC__
+ memset_explicit(elems, 0, buffer_size);
+#else
+ memset(elems, 0, buffer_size);
+#endif
+ // Clean the data cache so that the data gets zeroized in main memory right away. Without this,
+ // it might not be written to main memory until the cache line happens to be evicted.
+ CleanDataCache(static_cast<const uint8_t*>(elems), buffer_size, cache_line_size);
+
+ env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0);
+}
+
+static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) {
+ ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte));
+}
+
+static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) {
+ ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar));
+}
+
+static const JNINativeMethod sMethods[] = {
+ {"zeroize", "([B)V", (void*)ZeroizeByteArray},
+ {"zeroize", "([C)V", (void*)ZeroizeCharArray},
+};
+
+int register_com_android_internal_util_ArrayUtils(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods,
+ NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 16d25657f08e..e424e82b94cd 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,6 +218,7 @@ message SystemSettingsProto {
optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
}
optional Touchpad touchpad = 36;
diff --git a/core/res/Android.bp b/core/res/Android.bp
index aacd8699c202..be4fb8bdecfb 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -162,6 +162,7 @@ android_app {
"android.appwidget.flags-aconfig",
"android.companion.virtualdevice.flags-aconfig",
"android.content.pm.flags-aconfig",
+ "android.location.flags-aconfig",
"android.media.audio-aconfig",
"android.provider.flags-aconfig",
"camera_platform_flags",
@@ -178,6 +179,7 @@ android_app {
"art-aconfig-flags",
"ranging_aconfig_flags",
"aconfig_settingslib_flags",
+ "telephony_flags",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2f3d2a32d0b..6b0569041edd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1139,6 +1139,16 @@
android:protectionLevel="signature|privileged|vendorPrivileged"
android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+ <!--
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ Allows an application to read the aggregated color zones on the screen for use cases like
+ TV ambient backlight usages.
+ <p> Protection level: normal
+ -->
+ <permission android:name="android.permission.READ_COLOR_ZONES"
+ android:protectionLevel="normal"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
<!-- ====================================================================== -->
<!-- Permissions for accessing external storage -->
<!-- ====================================================================== -->
@@ -2145,6 +2155,21 @@
<permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to bind to a
+ android.service.PopulationDensityProviderService for the purpose of
+ querying population density. This prevents arbitrary clients connecting
+ to the service. The system server checks that the provider's intent
+ service explicitly sets this permission via the android:permission
+ attribute of the service.
+ This is only expected to be possessed by the system server outside of
+ tests.
+ @FlaggedApi(android.location.flags.Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE"
+ android:featureFlag="android.location.flags.population_density_provider"
+ android:protectionLevel="signature" />
+
<!-- ======================================= -->
<!-- Permissions for accessing networks -->
<!-- ======================================= -->
@@ -4124,6 +4149,14 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to manage policy related to AppFunctions.
+ <p>Protection level: internal|role
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS"
+ android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to set policy related to subscriptions downloaded by an admin.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
@@ -7009,6 +7042,13 @@
<permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows for reading subscription plan fields for status and end date.
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+ -->
+ <permission android:name="android.permission.READ_SUBSCRIPTION_PLANS"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date" />
+
<!-- C2DM permission.
@hide Used internally.
-->
@@ -8696,7 +8736,7 @@
android:featureFlag="android.security.secure_lockdown" />
<!-- Allows app to enter trade-in-mode.
- <p>Protection level: signature|privileged
+ <p>Protection level: signature
@hide
-->
<permission android:name="android.permission.ENTER_TRADE_IN_MODE"
diff --git a/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
new file mode 100644
index 000000000000..665f47faca0d
--- /dev/null
+++ b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
@@ -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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?attr/disabledAlpha"
+ android:color="?attr/materialColorOnSurface" />
+ <item android:color="?attr/materialColorOutline" />
+</selector>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
new file mode 100644
index 000000000000..7bc40604dc25
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
@@ -0,0 +1,39 @@
+<!--
+ ~ 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <solid android:color="#fff"/>
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <stroke
+ android:width="1dp"
+ android:color="@color/btn_material_outlined_background_color" />
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+</ripple>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_text.xml b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
new file mode 100644
index 000000000000..145685c8095c
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+ <solid android:color="#fff"/>
+ <size
+ android:width="@dimen/btn_material_width"
+ android:height="@dimen/btn_material_height" />
+ </shape>
+ </item>
+</ripple>
diff --git a/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml
new file mode 100644
index 000000000000..5c0e5f606d81
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromDegrees = "270"
+ android:toDegrees="270"
+ android:pivotX="50%"
+ android:pivotY="50%" >
+ <layer-list>
+ <item>
+ <shape
+ android:shape="ring"
+ android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:thickness="@dimen/progressbar_thickness"
+ android:useLevel="false">
+ <solid android:color="?attr/materialColorSurfaceContainer"/>
+ </shape>
+ </item>
+ <item>
+ <shape
+ android:shape="ring"
+ android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+ android:thickness="@dimen/progressbar_thickness"
+ android:useLevel="true">
+ <solid android:color="?attr/materialColorPrimary"/>
+ </shape>
+ </item>
+ </layer-list>
+</rotate> \ No newline at end of file
diff --git a/core/res/res/layout-watch-v36/alert_dialog_material.xml b/core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml
index 8f7545690142..8f7545690142 100644
--- a/core/res/res/layout-watch-v36/alert_dialog_material.xml
+++ b/core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml
diff --git a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
new file mode 100644
index 000000000000..b25adaabf8e8
--- /dev/null
+++ b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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 underthe License
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/conversation_face_pile"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:forceHasOverlappingRendering="false"
+ >
+ <ImageView
+ android:id="@+id/conversation_face_pile_top"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:scaleType="centerCrop"
+ android:layout_gravity="end|top"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|bottom">
+ <ImageView
+ android:id="@+id/conversation_face_pile_bottom_background"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/conversation_badge_background"
+ />
+ <ImageView
+ android:id="@+id/conversation_face_pile_bottom"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:scaleType="centerCrop"
+ android:layout_gravity="center"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ />
+ </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml
new file mode 100644
index 000000000000..90befd911bdf
--- /dev/null
+++ b/core/res/res/layout/notification_2025_conversation_icon_container.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/conversation_icon_container"
+ android:layout_width="@dimen/conversation_content_start"
+ android:layout_height="wrap_content"
+ android:gravity="start|top"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingTop="20dp"
+ android:paddingBottom="16dp"
+ android:importantForAccessibility="no"
+ >
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_gravity="top|center_horizontal"
+ >
+
+ <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+ android:layout_marginRight="@dimen/conversation_badge_protrusion"
+ android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+
+ <ViewStub
+ android:layout="@layout/notification_2025_conversation_face_pile_layout"
+ android:layout_width="@dimen/conversation_avatar_size"
+ android:layout_height="@dimen/conversation_avatar_size"
+ android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+ android:layout_marginRight="@dimen/conversation_badge_protrusion"
+ android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+ android:id="@+id/conversation_face_pile"
+ />
+
+ <FrameLayout
+ android:id="@+id/conversation_icon_badge"
+ android:layout_width="@dimen/conversation_icon_size_badged"
+ android:layout_height="@dimen/conversation_icon_size_badged"
+ android:layout_gravity="end|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ >
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_background"
+ android:forceHasOverlappingRendering="false"
+ android:scaleType="center"
+ />
+
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="4dp"
+ android:layout_gravity="center"
+ android:forceHasOverlappingRendering="false"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon_badge_ring"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@drawable/conversation_badge_ring"
+ android:visibility="gone"
+ android:forceHasOverlappingRendering="false"
+ android:clipToPadding="false"
+ android:scaleType="center"
+ />
+ </FrameLayout>
+ </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_messaging_group.xml b/core/res/res/layout/notification_2025_messaging_group.xml
new file mode 100644
index 000000000000..c1b491fc4b0e
--- /dev/null
+++ b/core/res/res/layout/notification_2025_messaging_group.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<!-- extends LinearLayout -->
+<com.android.internal.widget.MessagingGroup
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <FrameLayout
+ android:id="@+id/message_icon_container"
+ android:layout_width="@dimen/conversation_content_start"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:layout_gravity="top|center_horizontal"
+ android:id="@+id/message_icon"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no" />
+ </FrameLayout>
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/messaging_group_content_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:baselineAligned="true"
+ android:orientation="vertical">
+ <com.android.internal.widget.ImageFloatingTextView
+ android:id="@+id/message_name"
+ style="@style/Widget.DeviceDefault.Notification.MessagingName"
+ android:layout_width="wrap_content"
+ android:textAlignment="viewStart"
+ />
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/group_message_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_text_margin_top"
+ android:spacing="2dp" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+ <FrameLayout
+ android:id="@+id/messaging_group_icon_container"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:layout_marginStart="12dp"
+ android:visibility="gone"/>
+ <FrameLayout
+ android:id="@+id/messaging_group_sending_progress_container"
+ android:layout_width="@dimen/messaging_group_sending_progress_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:layout_marginStart="12dp"
+ android:layout_gravity="top"
+ android:visibility="gone">
+ <ProgressBar
+ android:id="@+id/messaging_group_sending_progress"
+ android:layout_height="@dimen/messaging_group_sending_progress_size"
+ android:layout_width="@dimen/messaging_group_sending_progress_size"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleSmall" />
+ </FrameLayout>
+</com.android.internal.widget.MessagingGroup>
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 a790e5da20de..76c810bdb2c1 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:minHeight="@dimen/notification_headerless_min_height"
+ android:minHeight="@dimen/notification_2025_min_height"
android:tag="base"
>
@@ -28,8 +28,8 @@
android:id="@+id/left_icon"
android:layout_width="@dimen/notification_2025_left_icon_size"
android:layout_height="@dimen/notification_2025_left_icon_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -41,8 +41,8 @@
android:id="@+id/icon"
android:layout_width="@dimen/notification_2025_icon_circle_size"
android:layout_height="@dimen/notification_2025_icon_circle_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_2025_icon_circle_padding"
android:maxDrawableWidth="@dimen/notification_2025_icon_circle_size"
@@ -72,8 +72,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
android:orientation="vertical"
>
@@ -81,7 +81,7 @@
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_headerless_line_height"
+ android:minHeight="@dimen/notification_2025_content_min_height"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
new file mode 100644
index 000000000000..614444d6b2f0
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<!-- Extends FrameLayout -->
+<com.android.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_2025_conversation_icon_container" />
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_2025_min_height"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/notification_2025_margin"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_text_height"
+ />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include
+ layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
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 427c4e42e40c..2e0a7afc3cd1 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -23,7 +23,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_min_height"
+ android:minHeight="@dimen/notification_2025_min_height"
android:tag="media"
>
@@ -32,8 +32,8 @@
android:id="@+id/left_icon"
android:layout_width="@dimen/notification_2025_left_icon_size"
android:layout_height="@dimen/notification_2025_left_icon_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -45,8 +45,8 @@
android:id="@+id/icon"
android:layout_width="@dimen/notification_2025_icon_circle_size"
android:layout_height="@dimen/notification_2025_icon_circle_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_2025_icon_circle_padding"
/>
@@ -74,8 +74,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
android:orientation="vertical"
>
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 f0e4c0f272fb..f644adefda9d 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -38,7 +38,7 @@
<com.android.internal.widget.NotificationMaxHeightFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="@dimen/notification_min_height"
+ android:minHeight="@dimen/notification_2025_min_height"
android:clipChildren="false"
>
@@ -46,8 +46,8 @@
android:id="@+id/left_icon"
android:layout_width="@dimen/notification_2025_left_icon_size"
android:layout_height="@dimen/notification_2025_left_icon_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -59,8 +59,8 @@
android:id="@+id/icon"
android:layout_width="@dimen/notification_2025_icon_circle_size"
android:layout_height="@dimen/notification_2025_icon_circle_size"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:layout_alignParentStart="true"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_2025_icon_circle_padding"
/>
@@ -98,8 +98,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginBottom="@dimen/notification_2025_margin"
+ android:layout_marginTop="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:clipChildren="false"
android:orientation="vertical"
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
new file mode 100644
index 000000000000..0c4c7fba90b1
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_conversation.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="conversation"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <include layout="@layout/notification_2025_conversation_icon_container" />
+
+ <!-- Wraps entire "expandable" notification -->
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+ <!-- LinearLayout for Expand Button-->
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/expand_button_and_content_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="0dp"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <!-- Header -->
+
+ <!-- Use layout_marginStart instead of paddingStart to work around strange
+ measurement behavior on lower display densities. -->
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="2dp"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ />
+
+ <!-- Messages -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_text_size"
+ android:spacing="@dimen/notification_messaging_spacing"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <!-- This is where the expand button container will be placed when collapsed-->
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ <include layout="@layout/notification_material_action_list" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded.
+ The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to
+ its sibling view in accessibility focus order.
+ {see android.view.ViewGroup.addChildrenForAccessibility()}
+ expand_button_container will be moved under expand_button_and_content_container when collapsed,
+ this dynamic movement ensures message can flow under expand button when expanded-->
+ <FrameLayout
+ android:id="@+id/expand_button_a11y_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|top"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_marginTop="-1px"
+ android:paddingTop="1px"
+ >
+ <!--expand_button_container is dynamically placed between here and at the end of the
+ layout. It starts here since only FrameLayout layout params have gravity-->
+ <LinearLayout
+ android:id="@+id/expand_button_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|top"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="end"
+ android:layout_marginEnd="20dp"
+ />
+ <!--expand_button_touch_container makes sure that we can nicely center the expand
+ content in the collapsed layout while the parent makes sure that we're never laid out
+ bigger than the messaging content.-->
+ <LinearLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/conversation_expand_button_height"
+ android:orientation="horizontal"
+ android:layout_gravity="end|top"
+ android:paddingEnd="0dp"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ >
+ <!-- Images -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/conversation_image_message_container"
+ android:forceHasOverlappingRendering="false"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginStart="@dimen/conversation_image_start_margin"
+ android:spacing="0dp"
+ android:layout_gravity="center"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ />
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ />
+ </LinearLayout>
+ </LinearLayout>
+ </FrameLayout>
+</com.android.internal.widget.ConversationLayout>
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
new file mode 100644
index 000000000000..18bafe068fcb
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tag="bigPicture"
+ android:clipChildren="false"
+ >
+
+ <include layout="@layout/notification_2025_template_header" />
+
+ <include layout="@layout/notification_template_right_icon" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ >
+
+ <include layout="@layout/notification_template_part_line1" />
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ />
+
+ <include layout="@layout/notification_template_text_multiline" />
+ </LinearLayout>
+
+ <com.android.internal.widget.BigPictureNotificationImageView
+ android:id="@+id/big_picture"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:layout_weight="1"
+ android:layout_marginTop="13dp"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:background="@drawable/notification_big_picture_outline"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
+ />
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_2025_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
new file mode 100644
index 000000000000..9ff141b7c946
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="bigText"
+ >
+
+ <include layout="@layout/notification_2025_template_header" />
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginBottom="@dimen/notification_content_margin"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingStart="@dimen/notification_2025_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ >
+
+ <include layout="@layout/notification_template_part_line1" />
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ android:layout_marginBottom="6dp"
+ />
+
+ <com.android.internal.widget.ImageFloatingTextView
+ android:id="@+id/big_text"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_text_margin_top"
+ android:singleLine="false"
+ android:gravity="top"
+ android:visibility="gone"
+ android:textAlignment="viewStart"
+ />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_2025_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_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
new file mode 100644
index 000000000000..3ff71b78835d
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<!-- Extends FrameLayout -->
+<com.android.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_2025_conversation_icon_container" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_content_margin"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:minHeight="68dp"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text_multiline" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include
+ layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_material_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
new file mode 100644
index 000000000000..9fb44ccccfa0
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:tag="inbox"
+ android:clipChildren="false"
+ >
+ <include layout="@layout/notification_2025_template_header" />
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:paddingStart="@dimen/notification_2025_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_weight="1"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ >
+ <include layout="@layout/notification_template_part_line1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <include layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ android:layout_marginBottom="2dp"/>
+ <TextView android:id="@+id/inbox_text0"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text1"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text2"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text3"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text4"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text5"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ <TextView android:id="@+id/inbox_text6"
+ style="@style/Widget.DeviceDefault.Notification.Text"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+ </LinearLayout>
+ <ViewStub android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_2025_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+ <include layout="@layout/notification_template_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
new file mode 100644
index 000000000000..578a0b2b6d0b
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_media.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<!-- Note: This is the expanded version of the old media style notification (different from UMO). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MediaNotificationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:tag="bigMediaNarrow"
+ >
+
+ <include layout="@layout/notification_2025_template_header" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:id="@+id/notification_media_content"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ >
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_text"/>
+ </LinearLayout>
+
+ <!-- this FrameLayout's minHeight serves as a padding for the content above -->
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start"
+ android:minHeight="@dimen/notification_content_margin"
+ >
+
+ <!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies
+ at the start instead of always the left, given that the media_actions LinearLayout
+ has layoutDirection="ltr". -->
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/media_notification_actions_padding_bottom"
+ android:gravity="top"
+ android:orientation="horizontal"
+ android:layoutDirection="ltr"
+ >
+
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action3"
+ />
+
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action4"
+ />
+ </LinearLayout>
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/notification_template_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
new file mode 100644
index 000000000000..5b5872657a43
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:tag="messaging"
+ >
+
+ <include layout="@layout/notification_2025_template_header"/>
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipChildren="false"
+ android:orientation="vertical">
+
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </com.android.internal.widget.RemeasuringLinearLayout>
+
+ <include layout="@layout/notification_template_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
new file mode 100644
index 000000000000..afa4bc6dd7f8
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:tag="progress"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_content_margin"
+ android:orientation="vertical"
+ >
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ >
+
+ <include layout="@layout/notification_2025_template_header" />
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:orientation="vertical"
+ >
+
+ <include layout="@layout/notification_template_part_line1" />
+
+ <include layout="@layout/notification_template_text_multiline" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ android:orientation="horizontal">
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/notification_progress_start_icon"
+ android:layout_width="@dimen/notification_progress_icon_size"
+ android:layout_height="@dimen/notification_progress_icon_size"
+ android:background="@drawable/notification_progress_icon_background"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:layout_marginEnd="@dimen/notification_progress_margin_horizontal"
+ android:scaleType="centerCrop"
+ android:maxDrawableWidth="@dimen/notification_progress_icon_size"
+ android:maxDrawableHeight="@dimen/notification_progress_icon_size"
+ />
+
+
+ <include
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="@dimen/notification_progress_tracker_height"
+ layout="@layout/notification_template_notification_progress_bar"
+ />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/notification_progress_end_icon"
+ android:layout_width="@dimen/notification_progress_icon_size"
+ android:layout_height="@dimen/notification_progress_icon_size"
+ android:background="@drawable/notification_progress_icon_background"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:layout_marginStart="@dimen/notification_progress_margin_horizontal"
+ android:maxDrawableWidth="@dimen/notification_progress_icon_size"
+ android:maxDrawableHeight="@dimen/notification_progress_icon_size"
+ />
+ </LinearLayout>
+ </LinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+ </FrameLayout>
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_2025_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index b7fe454e09d4..63872aff8dd0 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -33,8 +33,7 @@
android:layout_width="@dimen/notification_2025_left_icon_size"
android:layout_height="@dimen/notification_2025_left_icon_size"
android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
@@ -47,8 +46,7 @@
android:layout_width="@dimen/notification_2025_icon_circle_size"
android:layout_height="@dimen/notification_2025_icon_circle_size"
android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:layout_margin="@dimen/notification_2025_margin"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_2025_icon_circle_padding"
android:maxDrawableWidth="@dimen/notification_2025_icon_circle_size"
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 78299ab0ea99..7bb6fcfa2ae0 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -25,7 +25,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="6"
- android:paddingBottom="10dp"
+ android:paddingBottom="16dp"
android:text="@string/fp_power_button_enrollment_title"
android:textColor="@color/side_fps_text_color"
android:paddingLeft="20dp"/>
@@ -37,7 +37,7 @@
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="3"
- android:paddingBottom="10dp"
+ android:paddingBottom="16dp"
android:text="@string/fp_power_button_enrollment_button_text"
style="?android:attr/buttonBarNegativeButtonStyle"
android:textColor="@color/side_fps_button_color"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index a4499efae37d..edb926c5a30c 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Beller-ID se verstek is beperk. Volgende oproep: nie beperk nie"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Beller-ID se verstek is nie beperk nie. Volgende oproep: beperk"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID se verstek is nie beperk nie. Volgende oproep: nie beperk nie"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Hierdie app is nie versoenbaar met 16 KB nie. APK-belyningkontrole het misluk. Hierdie app sal met bladsygrootteversoenbare modus gebruik word. Stel asseblief weer die app met 16 KB-ondersteuning saam vir beste versoenbaarheid. Sien &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; vir meer inligting"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Hierdie app is nie versoenbaar met 16 KB nie. ELF-belyningkontrole het misluk. Hierdie app sal met bladsygrootteversoenbare modus gebruik word. Stel asseblief weer die app met 16 KB-ondersteuning saam vir beste versoenbaarheid. Sien &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; vir meer inligting"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Hierdie app is nie versoenbaar met 16 KB nie. APK- ELF-belyningkontroles het misluk. Hierdie app sal met bladsygrootteversoenbare modus gebruik word. Stel asseblief weer die app met 16 KB-ondersteuning saam vir beste versoenbaarheid. Sien &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; vir meer inligting"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Diens nie verskaf nie."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Jy kan nie die beller-ID-instelling verander nie."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Het data oorgeskakel na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -302,7 +305,7 @@
<string name="notification_channel_alerts" msgid="5070241039583668427">"Opletberigte"</string>
<string name="notification_channel_retail_mode" msgid="3732239154256431213">"Kleinhandeldemonstrasie"</string>
<string name="notification_channel_usb" msgid="1528280969406244896">"USB-verbinding"</string>
- <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Program loop tans"</string>
+ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"App loop tans"</string>
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programme wat batterykrag gebruik"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toeganklikheidgebruik"</string>
@@ -374,13 +377,13 @@
<string name="permlab_install_shortcut" msgid="7451554307502256221">"installeer kortpaaie"</string>
<string name="permdesc_install_shortcut" msgid="4476328467240212503">"Stel \'n program in staat om Tuisskerm-kortpaaie by te voeg sonder gebruikerinmenging."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"deïnstalleer kortpaaie"</string>
- <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Laat die program toe om Tuisskerm-kortpaaie te verwyder sonder gebruikerinmenging."</string>
+ <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Laat die app toe om Tuisskerm-kortpaaie te verwyder sonder gebruikerinmenging."</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"herlei uitgaande oproepe"</string>
<string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Laat die program toe om te sien watter nommer tydens \'n uitgaande oproep geskakel word, met die opsie om die oproep na \'n ander nommer te herlei of die oproep heeltemal te beëindig."</string>
<string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"antwoord foonoproepe"</string>
<string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Laat die program toe om inkomende foonoproepe te antwoord."</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"ontvang teksboodskappe (SMS)"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"Laat die program toe om SMS-boodskappe te ontvang en te verwerk. Dit beteken dat die program boodskappe wat na jou toestel gestuur is, kan monitor of uitvee, sonder dat jy dit gesien het."</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"Laat die app toe om SMS-boodskappe te ontvang en te verwerk. Dit beteken dat die app boodskappe wat na jou toestel gestuur is, kan monitor of uitvee, sonder dat jy dit gesien het."</string>
<string name="permlab_receiveMms" msgid="4000650116674380275">"ontvang teksboodskappe (MMS)"</string>
<string name="permdesc_receiveMms" msgid="958102423732219710">"Laat die program toe om MMS-boodskappe te ontvang en te verwerk. Dit beteken dat die program boodskappe wat na jou toestel gestuur is kan monitor of uitvee, sonder dat jy dit gesien het."</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Stuur seluitsendingboodskappe aan"</string>
@@ -397,10 +400,10 @@
<string name="permdesc_sendSms" msgid="6757089798435130769">"Laat die program toe om SMS-boodskappe te stuur. Dit kan tot onverwagse heffings lei. Kwaadwillige programme kan jou geld kos deur boodskappe sonder jou bevestiging te stuur."</string>
<string name="permlab_readSms" msgid="5164176626258800297">"lees jou teksboodskappe (SMS of MMS)"</string>
<string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Hierdie program kan alle SMS\'e (teksboodskappe) wat op jou tablet geberg is, lees."</string>
- <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Hierdie program kan alle SMS- (teks)-boodskappe lees wat op jou Android TV-toestel geberg is."</string>
+ <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Hierdie app kan alle SMS- (teks)-boodskappe lees wat op jou Android TV-toestel geberg is."</string>
<string name="permdesc_readSms" product="default" msgid="774753371111699782">"Hierdie program kan alle SMS\'e (teksboodskappe) wat op jou foon geberg is, lees."</string>
<string name="permlab_receiveWapPush" msgid="4223747702856929056">"ontvang teksboodskappe (WAP)"</string>
- <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Laat die program toe om WAP-boodskappe te ontvang en te verwerk. Hierdie toestemming sluit ook in dat boodskappe wat na jou toestel gestuur is, gemonitor of uitgevee kan word, sonder dat jy dit gesien het."</string>
+ <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Laat die app toe om WAP-boodskappe te ontvang en te verwerk. Hierdie toestemming sluit ook in dat boodskappe wat na jou toestel gestuur is, gemonitor of uitgevee kan word, sonder dat jy dit gesien het."</string>
<string name="permlab_getTasks" msgid="7460048811831750262">"haal lopende programme op"</string>
<string name="permdesc_getTasks" msgid="7388138607018233726">"Laat die program toe om inligting oor die huidig- en onlangslopende take op te haal. Dit kan moontlik die program toelaat om inligting oor watter programme op die toestel gebruik word, te ontdek."</string>
<string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"bestuur profiel- en toesteleienaars"</string>
@@ -410,15 +413,15 @@
<string name="permlab_enableCarMode" msgid="893019409519325311">"aktiveer motormodus"</string>
<string name="permdesc_enableCarMode" msgid="56419168820473508">"Laat die program toe om die motormodus te aktiveer."</string>
<string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"maak ander programme toe"</string>
- <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Laat die program toe om agtergrondprosesse van ander programme te beëindig. Dit kan moontlik veroorsaak dat ander programme ophou werk."</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Laat die app toe om agtergrondprosesse van ander apps te beëindig. Dit kan moontlik veroorsaak dat ander apps ophou werk."</string>
<string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Hierdie program kan bo-op ander programme verskyn"</string>
- <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Hierdie program kan bokant ander programme of ander dele van die skerm verskyn. Dit kan met normale programgebruik inmeng en die voorkoms van ander programme verander."</string>
+ <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Hierdie app kan bokant ander apps of ander dele van die skerm verskyn. Dit kan met normale appgebruik inmeng en die voorkoms van ander apps verander."</string>
<string name="permlab_hideOverlayWindows" msgid="6382697828482271802">"versteek ander apps se oorleggers"</string>
<string name="permdesc_hideOverlayWindows" msgid="5660242821651958225">"Hierdie app kan versoek dat die stelsel oorleggers wat oorspronklik van apps af kom, versteek sodat hulle nie bo-op hulle wys nie."</string>
<string name="permlab_runInBackground" msgid="541863968571682785">"loop op die agtergrond"</string>
<string name="permdesc_runInBackground" msgid="4344539472115495141">"Hierdie program kan op die agtergrond loop. Dit kan die battery vinniger laat pap word."</string>
<string name="permlab_useDataInBackground" msgid="783415807623038947">"gebruik data op die agtergrond"</string>
- <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Hierdie program kan data op die agtergrond gebruik. Dit kan die datagebruik vergroot."</string>
+ <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Hierdie app kan data op die agtergrond gebruik. Dit kan die datagebruik vergroot."</string>
<string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"Skeduleer handelinge met presiese tydsbesturing"</string>
<string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"Hierdie app kan werk skeduleer om op ’n gewenste tyd in die toekoms plaas te vind. Dit beteken ook dat die app kan werk wanneer die toestel nie aktief gebruik word nie."</string>
<string name="permlab_use_exact_alarm" msgid="348045139777131552">"Skeduleer wekkers of geleentheidonthounotas"</string>
@@ -426,7 +429,7 @@
<string name="permlab_persistentActivity" msgid="464970041740567970">"laat program altyd loop"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Laat die program toe om dele van ditself deurdringend in die geheue te hou. Dit kan geheue wat aan ander programme beskikbaar is, beperk, en die tablet stadiger maak."</string>
<string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Laat die program toe om dele daarvan in die geheue te laat voortbestaan. Dit kan geheue wat vir ander programme beskikbaar is, beperk en sodoende jou Android TV-toestel stadiger maak."</string>
- <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Laat die program toe om dele van ditself deurdringend in die geheue te hou. Dit kan geheue wat aan ander programme beskikbaar is, beperk, en die foon stadiger maak."</string>
+ <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Laat die app toe om dele van die app deurdringend in die geheue te hou. Dit kan geheue wat aan ander apps beskikbaar is, beperk, en die foon stadiger maak."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"laat loop voorgronddiens"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Laat die program toe om van voorgronddienste gebruik te maak."</string>
<string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"gebruik voorgronddienstipe “kamera”"</string>
@@ -466,9 +469,9 @@
<string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Laat die program toe om self te begin sodra die stelsel klaar geselflaai het. Dit kan dalk daartoe lei dat die toestel langer neem om jou Android TV-toestel te begin, en laat die program toe om die hele toestel stadiger te maak deur altyd te loop."</string>
<string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"Laat die program toe om homself te begin so gou as moontlik nadat die stelsel laai. Dit maak dat dit langer neem vir die foon om te begin, en dit laat die foon toe om die foon stadiger te maak omdat dit altyd loop."</string>
<string name="permlab_broadcastSticky" msgid="4552241916400572230">"Stuur klewerige uitsending"</string>
- <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Laat die program toe om taai uitsendings te stuur, wat agterbly nadat die uitsending klaar is. Oormatige gebruik kan die tablet stadig of onstabiel maak deurdat dit te veel geheue gebruik."</string>
- <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Laat die program toe om vaste uitsendings wat agterbly nadat die uitsending eindig, te stuur. Oormatige gebruik kan jou Android TV-toestel stadig of onstabiel maak omdat dit veroorsaak dat jou toestel te veel geheue gebruik."</string>
- <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Laat die program toe om taai uitsendings te stuur, wat agterbly nadat die uitsending klaar is. Oormatige gebruik kan die foon stadig of onstabiel maak deurdat dit te veel geheue gebruik."</string>
+ <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Laat die app toe om vaste uitsendings te stuur, wat agterbly nadat die uitsending klaar is. Oormatige gebruik kan die tablet stadig of onstabiel maak deurdat dit te veel geheue gebruik."</string>
+ <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Laat die app toe om vaste uitsendings wat agterbly nadat die uitsending eindig, te stuur. Oormatige gebruik kan jou Android TV-toestel stadig of onstabiel maak omdat dit veroorsaak dat jou toestel te veel geheue gebruik."</string>
+ <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Laat die app toe om vaste uitsendings te stuur, wat agterbly nadat die uitsending klaar is. Oormatige gebruik kan die foon stadig of onstabiel maak deurdat dit te veel geheue gebruik."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lees jou kontakte"</string>
<string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Laat die program toe om data te lees oor jou kontakte wat op jou tablet geberg is. Programme sal ook toegang hê tot die rekeninge op jou tablet wat kontakte geskep het. Dit kan rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
<string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Laat die program toe om data te lees oor jou kontakte wat op jou Android TV-toestel geberg is. Programme sal ook toegang hê tot die rekeninge op jou Android TV-toestel wat kontakte geskep het. Dit kan rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
@@ -480,7 +483,7 @@
<string name="permlab_readCallLog" msgid="1739990210293505948">"lees oproeprekord"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Hierdie program kan jou oproepgeskiedenis lees."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"skryf oproeprekord"</string>
- <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Laat die program toe om jou tablet se oproeprekord, insluitende data oor inkomende en uitgaande oproepe, te verander. Kwaadwillige programme kan dit gebruik om jou oproeprekord uit te vee of te verander."</string>
+ <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Laat die app toe om jou tablet se oproeprekord, insluitende data oor inkomende en uitgaande oproepe, te verander. Kwaadwillige apps kan dit gebruik om jou oproeprekord uit te vee of te verander."</string>
<string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Laat die program toe om jou Android TV-toestel se oproeprekord te wysig, insluitend data oor inkomende en uitgaande oproepe. Kwaadwillige programme kan dit gebruik om jou oproeprekord uit te vee of te wysig."</string>
<string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Laat die program toe om jou foon se oproeprekord, insluitende data oor inkomende en uitgaande oproepe, te verander. Kwaadwillige programme kan dit gebruik om jou oproeprekord uit te vee of te verander."</string>
<string name="permlab_bodySensors" msgid="662918578601619569">"Kry toegang tot liggaamsensordata, soos polsslag, terwyl program gebruik word"</string>
@@ -496,7 +499,7 @@
<string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Hierdie program kan kalendergeleenthede op jou Android TV-toestel byvoeg, verwyder of verander. Hierdie program kan boodskappe stuur wat lyk of dit van kalendereienaars af kom of geleenthede verander sonder om hul eienaars in kennis te stel."</string>
<string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"Hierdie program kan kalendergebeurtenisse op jou foon byvoeg, verwyder of verander. Hierdie program kan boodskappe stuur wat lyk of dit van kalendereienaars af kom of gebeurtenisse verander sonder om hul eienaars in te lig."</string>
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"Kry toegang tot ekstra liggingverskaffer-bevele"</string>
- <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Gee die program toegang tot ekstra liggingverskaffer-bevele. Dit kan die program dalk toelaat om in te meng met die werking van die GPS of ander liggingbronne."</string>
+ <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Gee die app toegang tot ekstra liggingverskaffer-bevele. Dit kan die app dalk toelaat om in te meng met die werking van die GPS of ander liggingbronne."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"kry net op die voorgrond toegang tot presiese ligging"</string>
<string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Hierdie program kan jou presiese ligging van liggingdienste af kry terwyl die program gebruik word. Liggingdienste vir jou toestel moet aangeskakel wees vir die program om die ligging te kry. Dit kan batterygebruik verhoog."</string>
<string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"kry benaderde ligging net op die voorgrond"</string>
@@ -519,11 +522,11 @@
<string name="permdesc_camera" msgid="5240801376168647151">"Hierdie program kan met die kamera foto\'s neem en video\'s opneem terwyl die program gebruik word."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"neem foto\'s en video\'s op die agtergrond"</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Hierdie program kan enige tyd met die kamera foto\'s neem en video\'s opneem."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"Gee \'n program of diens toegang tot stelselkameras om foto\'s en video\'s te neem"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"Gee \'n app of diens toegang tot stelselkameras om foto\'s en video\'s te neem"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Hierdie bevoorregte of stelselprogram kan enige tyd met \'n stelselkamera foto\'s neem en video\'s opneem. Vereis dat die program ook die android.permission.CAMERA-toestemming het"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Laat \'n program of diens toe om terugbeloproepe te ontvang oor kameratoestelle wat oopgemaak of toegemaak word."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Hierdie program kan terugbeloproepe ontvang wanneer enige kameratoestel oopgemaak (deur watter program) of toegemaak word."</string>
- <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Laat ’n program of diens toe om toegang tot die kamera te verkry as ’n stelselgebruiker sonder koppelvlak."</string>
+ <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Laat ’n app of diens toe om toegang tot die kamera te verkry as ’n stelselgebruiker sonder koppelvlak."</string>
<string name="permdesc_cameraHeadlessSystemUser" msgid="6963163319710996412">"Hierdie app het toegang tot die kamera as ’n stelselgebruiker sonder koppelvlak."</string>
<string name="permlab_vibrate" msgid="8596800035791962017">"beheer vibrasie"</string>
<string name="permdesc_vibrate" msgid="8733343234582083721">"Laat die program toe om die vibrator te beheer."</string>
@@ -542,7 +545,7 @@
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Laat die program toe om deurlopende oproepe op die toestel te sien en te beheer. Dit sluit inligting in soos oproepnommers vir oproepe en die toedrag van die oproepe."</string>
<string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"vrygestel van beperkings op oudio-opnames"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Stel die program vry van beperkings om oudio op te neem."</string>
- <string name="permlab_acceptHandover" msgid="2925523073573116523">"gaan voort met \'n oproep uit \'n ander program"</string>
+ <string name="permlab_acceptHandover" msgid="2925523073573116523">"gaan voort met \'n oproep uit \'n ander app"</string>
<string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Laat die program toe om \'n oproep voort te sit wat in \'n ander program begin is."</string>
<string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"lees foonnommers"</string>
<string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Laat die program toe om toegang tot die toestel se foonnommers te kry."</string>
@@ -551,27 +554,27 @@
<string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"keer dat jou Android TV-toestel slaap"</string>
<string name="permlab_wakeLock" product="default" msgid="569409726861695115">"verhoed foon om te slaap"</string>
<string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"Laat die program toe om die motorskerm aan te hou."</string>
- <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Laat die program toe om die tablet te keer om te slaap."</string>
- <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Laat die program toe om te keer dat jou Android TV-toestel slaap."</string>
- <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Laat die program toe om die foon te keer om te slaap."</string>
+ <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Laat die app toe om die tablet te keer om te slaap."</string>
+ <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Laat die app toe om te keer dat jou Android TV-toestel slaap."</string>
+ <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Laat die app toe om die foon te keer om te slaap."</string>
<string name="permlab_transmitIr" msgid="8077196086358004010">"versend infrarooi"</string>
<string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Laat die program toe om die tablet se infrarooisender te gebruik."</string>
- <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Laat die program toe om jou Android TV-toestel se infrarooisender te gebruik."</string>
+ <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Laat die app toe om jou Android TV-toestel se infrarooisender te gebruik."</string>
<string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Laat die program toe om die foon se infrarooisender te gebruik."</string>
<string name="permlab_setWallpaper" msgid="6959514622698794511">"stel muurpapier"</string>
- <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Laat die program toe om die stelsel se muurpapier te stel."</string>
+ <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Laat die app toe om die stelsel se muurpapier te stel."</string>
<string name="permlab_accessHiddenProfile" msgid="8607094418491556823">"Kry toegang tot versteekte profiele"</string>
<string name="permdesc_accessHiddenProfile" msgid="1543153202481009676">"Laat die app toe om toegang tot versteekte profiele te kry."</string>
<string name="permlab_setWallpaperHints" msgid="1153485176642032714">"verstel jou muurpapier se grootte"</string>
<string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"Laat die program toe om die stelsel se muurpapier se groottewenke te stel."</string>
<string name="permlab_setTimeZone" msgid="7922618798611542432">"stel tydsone"</string>
- <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Laat die program toe om die tablet se tydsone te verander."</string>
+ <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Laat die app toe om die tablet se tydsone te verander."</string>
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Laat die program toe om jou Android TV-toestel se tydsone te verander."</string>
<string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Laat die program toe om die foon se tydsone te verander."</string>
<string name="permlab_getAccounts" msgid="5304317160463582791">"soek rekeninge op die toestel"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Laat die program toe om die lys van rekeninge wat aan die tablet bekend is, te kry. Dit kan moontlik enige rekeninge wat geskep is deur programme wat jy geïnstalleer het, insluit."</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Laat die app toe om die lys van rekeninge wat aan die tablet bekend is, te kry. Dit kan moontlik enige rekeninge wat geskep is deur apps wat jy geïnstalleer het, insluit."</string>
<string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Laat die program toe om die lys rekeninge wat aan jou Android TV-toestel bekend is, te kry. Dit kan dalk rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het."</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Laat die program toe om die lys van rekeninge wat aan die foon bekend is, te kry. Dit kan moontlik enige rekeninge wat geskep is deur programme wat jy geïnstalleer het, insluit."</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Laat die app toe om die lys van rekeninge wat aan die foon bekend is, te kry. Dit kan moontlik enige rekeninge wat geskep is deur apps wat jy geïnstalleer het, insluit."</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"bekyk netwerkverbindings"</string>
<string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Laat die program toe om inligting oor netwerkverbindings, soos watter netwerke bestaan en gekoppel is, te sien."</string>
<string name="permlab_createNetworkSockets" msgid="3224420491603590541">"verkry volle netwerktoegang"</string>
@@ -579,29 +582,29 @@
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"verander netwerkverbinding"</string>
<string name="permdesc_changeNetworkState" msgid="649341947816898736">"Laat die program toe om die status van netwerkkonnektiwiteit te verander."</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"verander verbinde konnektiwiteit"</string>
- <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Laat die program toe om die status van verbinde netwerkkonnektiwiteit te verander."</string>
+ <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Laat die app toe om die status van verbinde netwerkkonnektiwiteit te verander."</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"bekyk Wi-Fi-verbindings"</string>
<string name="permdesc_accessWifiState" msgid="6913641669259483363">"Laat die program toe om inligting oor Wi-Fi-netwerke, soos of Wi-Fi geaktiveer is en die name van gekoppelde Wi-Fi toestelle, te sien."</string>
<string name="permlab_changeWifiState" msgid="7947824109713181554">"koppel en ontkoppel van Wi-Fi"</string>
<string name="permdesc_changeWifiState" msgid="7170350070554505384">"Laat die program toe om te koppel aan en te ontkoppel van Wi-Fi-toegangspunte en om veranderings aan Wi-Fi-netwerke se toestelopstellings te maak."</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"laat Wi-Fi-multisendontvangs toe"</string>
- <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Laat die program toe om pakkies te ontvang wat met behulp van multisaai-adresse na alle toestelle op \'n Wi-Fi-netwerk gestuur is, nie net jou tablet nie. Dit gebruik meer krag as die nie-multisaaimodus."</string>
- <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Laat die program toe om pakkette te ontvang wat met multi-uitsendingadresse na alle toestelle op \'n Wi-Fi-netwerk gestuur is, nie net jou Android TV-toestel nie. Dit gebruik meer krag as nie-multi-uitsendingmodus."</string>
+ <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Laat die app toe om pakkies te ontvang wat met behulp van multisaai-adresse na alle toestelle op \'n wi-fi-netwerk gestuur is, nie net jou tablet nie. Dit gebruik meer krag as die nie-multisaaimodus."</string>
+ <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Laat die app toe om pakkette te ontvang wat met multi-uitsendingadresse na alle toestelle op \'n wi-fi-netwerk gestuur is, nie net jou Android TV-toestel nie. Dit gebruik meer krag as nie-multi-uitsendingmodus."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Laat die program toe om pakkies te ontvang wat met behulp van multisaai-adresse na alle toestelle op \'n Wi-Fi-netwerk gestuur is, nie net jou foon nie. Dit gebruik meer krag as die nie-multisaaimodus."</string>
<string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"gaan in by Bluetooth-instellings"</string>
<string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Laat die program toe om die plaaslike Bluetooth-tablet op te stel, en om met afstandbeheer toestelle saam te bind."</string>
- <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Laat die program toe om Bluetooth op jou Android TV-toestel op te stel, en om afgeleë toestelle te ontdek en met hulle saam te bind."</string>
+ <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Laat die app toe om Bluetooth op jou Android TV-toestel op te stel, en om afgeleë toestelle te ontdek en met hulle saam te bind."</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Laat die program toe om die plaaslike Bluetooth-foon op te stel en te ontdek en met afgeleë toestelle saam te bind."</string>
<string name="permlab_accessWimaxState" msgid="7029563339012437434">"koppel aan en ontkoppel van WiMAX"</string>
<string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Laat die program toe om te bepaal of WiMAX geaktiveer is en of enige WiMAX-netwerke gekoppel is."</string>
<string name="permlab_changeWimaxState" msgid="6223305780806267462">"verander WiMAX-status"</string>
<string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Laat die program toe om die tablet aan WiMAX-netwerke te koppel en daarvan te ontkoppel."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Laat die program toe om jou Android TV-toestel aan WiMAX-netwerke te koppel en daarvan te ontkoppel."</string>
- <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Laat die program toe om die foon aan WiMAX-netwerke te koppel en daarvan te ontkoppel."</string>
+ <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Laat die app toe om die foon aan WiMAX-netwerke te koppel en daarvan te ontkoppel."</string>
<string name="permlab_bluetooth" msgid="586333280736937209">"bind saam met Bluetooth-toestelle"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Laat die program toe om die opstelling van Bluetooth op die tablet te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Laat die app toe om die opstelling van Bluetooth op die tablet te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Laat die program toe om die opstelling van Bluetooth op jou Android TV-toestel te bekyk, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
- <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Laat die program toe om die opstelling van die Bluetooth op die foon te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Laat die app toe om die opstelling van die Bluetooth op die foon te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
<string name="permlab_bluetooth_scan" msgid="5402587142833124594">"ontdek en bind Bluetooth-toestelle in die omtrek saam"</string>
<string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Laat die program toe om Bluetooth-toestelle in die omtrek te ontdek en saam te bind"</string>
<string name="permlab_bluetooth_connect" msgid="6657463246355003528">"koppel aan saamgebinde Bluetooth-toestelle"</string>
@@ -621,9 +624,9 @@
<string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Veilige Element-transaksiegeval"</string>
<string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Laat die app toe om inligting te ontvang oor transaksies wat op ’n Veilige Element plaasvind."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktiveer jou skermslot"</string>
- <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Laat die program toe om die sleutelslot en enige verwante wagwoordsekuriteit te deaktiveer. Byvoorbeeld, die foon deaktiveer die sleutelslot wanneer ’n oproep inkom, en atkiveer dit dan weer wanneer die oproep eindig."</string>
+ <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Laat die app toe om die sleutelslot en enige verwante wagwoordsekuriteit te deaktiveer. Byvoorbeeld, die foon deaktiveer die sleutelslot wanneer ’n oproep inkom, en aktiveer dit dan weer wanneer die oproep eindig."</string>
<string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"versoek skermslot-kompleksiteit"</string>
- <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Laat die program toe om die skermslot-kompleksiteitvlak (hoog, medium, laag of geen) te leer, wat die moontlike omvang van die lengte en soort skermslot aandui. Hierdie program kan ook aan gebruikers voorstel dat hulle die skermslot na \'n sekere vlak opdateer, maar gebruikers kan dit uit vrye wil ignoreer en weggaan. Let daarop dat die skermslot nie in skoonteks geberg word nie en die program dus nie die presiese wagwoord ken nie."</string>
+ <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Laat die app toe om die skermslot-kompleksiteitvlak (hoog, medium, laag of geen) te leer, wat die moontlike omvang van die lengte en soort skermslot aandui. Hierdie app kan ook aan gebruikers voorstel dat hulle die skermslot na \'n sekere vlak opdateer, maar gebruikers kan dit uit vrye wil ignoreer en weggaan. Let daarop dat die skermslot nie in skoonteks geberg word nie en die app dus nie die presiese wagwoord ken nie."</string>
<string name="permlab_postNotification" msgid="4875401198597803658">"wys kennisgewings"</string>
<string name="permdesc_postNotification" msgid="5974977162462877075">"Laat die program toe om kennisgewings te wys"</string>
<string name="permlab_turnScreenOn" msgid="219344053664171492">"skakel die skerm aan"</string>
@@ -631,7 +634,7 @@
<string name="permlab_useBiometric" msgid="6314741124749633786">"gebruik biometriese hardeware"</string>
<string name="permdesc_useBiometric" msgid="7502858732677143410">"Laat die program toe om biometriese hardeware vir stawing te gebruik"</string>
<string name="permlab_manageFingerprint" msgid="7432667156322821178">"bestuur vingerafdrukhardeware"</string>
- <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Laat die program toe om metodes te benut om vingerafdruksjablone vir gebruik by te voeg en uit te vee."</string>
+ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Laat die app toe om metodes te benut om vingerafdruktemplate vir gebruik by te voeg en uit te vee."</string>
<string name="permlab_useFingerprint" msgid="1001421069766751922">"gebruik vingerafdrukhardeware"</string>
<string name="permdesc_useFingerprint" msgid="412463055059323742">"Laat die program toe om vingerafdrukhardeware vir stawing te gebruik"</string>
<string name="permlab_audioWrite" msgid="8501705294265669405">"wysig jou musiekversameling"</string>
@@ -759,7 +762,7 @@
<string name="face_error_vendor_unknown" msgid="7387005932083302070">"Iets is fout. Probeer weer."</string>
<string name="face_icon_content_description" msgid="465030547475916280">"Gesig-ikoon"</string>
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"lees sinkroniseer-instellings"</string>
- <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Laat die program toe om die sinkroniseringinstellings van \'n rekening te lees. Byvoorbeeld, dit kan bepaal of die People-program met \'n rekening gesinkroniseer is."</string>
+ <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Laat die app toe om die sinkroniseringinstellings van \'n rekening te lees. Byvoorbeeld, dit kan bepaal of die People-app met \'n rekening gesinkroniseer is."</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"wissel tussen sinkronisasie aan en af"</string>
<string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Laat \'n program toe om die sinkroniseringinstellings van \'n rekening te verander. Byvoorbeeld, dit kan gebruik word om sinkronisasie van die People-program met \'n ander rekening te aktiveer."</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"lees sinkroniseerstatistiek"</string>
@@ -777,13 +780,13 @@
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"verander of vee jou gedeelde berging se inhoud uit"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Laat die program toe om jou gedeelde berging se inhoud te skryf."</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"maak en/of ontvang SIP-oproepe"</string>
- <string name="permdesc_use_sip" msgid="3590270893253204451">"Laat die program toe om SIP-oproepe te maak en te ontvang."</string>
+ <string name="permdesc_use_sip" msgid="3590270893253204451">"Laat die app toe om SIP-oproepe te maak en te ontvang."</string>
<string name="permlab_register_sim_subscription" msgid="1653054249287576161">"registreer nuwe telekommunikasie-SIM-verbindings"</string>
<string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"Laat die program toe om nuwe telekommunikasie-SIM-verbindings te registreer."</string>
<string name="permlab_register_call_provider" msgid="6135073566140050702">"registreer nuwe telekommunikasieverbindings"</string>
<string name="permdesc_register_call_provider" msgid="4201429251459068613">"Laat die program toe om nuwe telekommunikasieverbindings te registreer."</string>
<string name="permlab_connection_manager" msgid="3179365584691166915">"bestuur telekom-verbindings"</string>
- <string name="permdesc_connection_manager" msgid="1426093604238937733">"Laat die program toe om telekom-verbindings te bestuur."</string>
+ <string name="permdesc_connection_manager" msgid="1426093604238937733">"Laat die app toe om telekom-verbindings te bestuur."</string>
<string name="permlab_bind_incall_service" msgid="5990625112603493016">"beleef interaksie met inoproep-skerm"</string>
<string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Laat die program beheer wanneer en hoe die gebruiker die inoproep-skerm sien."</string>
<string name="permlab_bind_connection_service" msgid="5409268245525024736">"werk met telefoniedienste saam"</string>
@@ -791,9 +794,9 @@
<string name="permlab_control_incall_experience" msgid="6436863486094352987">"bied \'n inoproep-gebruikerervaring"</string>
<string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Laat die program toe om \'n inoproep-gebruikerervaring te bied."</string>
<string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"lees netwerkgebruik-geskiedenis"</string>
- <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Laat die program toe om historiese netwerkgebruik vir spesifieke netwerke en programme te lees."</string>
+ <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Laat die app toe om historiese netwerkgebruik vir spesifieke netwerke en apps te lees."</string>
<string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"bestuur netwerkbeleid"</string>
- <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Laat die program toe om netwerkbeleide te bestuur en program-spesifieke reëls te definieer."</string>
+ <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Laat die app toe om netwerkbeleide te bestuur en app-spesifieke reëls te definieer."</string>
<string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"verander verrekening van netwerkgebruik"</string>
<string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Laat die program toe om te verander hoe netwerkgebruik teenoor programme gemeet word. Nie vir gebruik deur normale programme nie."</string>
<string name="permlab_accessNotifications" msgid="7130360248191984741">"kry toegang tot kennisgewings"</string>
@@ -807,9 +810,9 @@
<string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"roep die opstellingprogram op wat deur die diensverskaffer voorsien is"</string>
<string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"Laat die houer toe om die opstellingsprogram wat deur die diensverskaffer voorsien word, op te roep. Behoort nooit vir gewone programme nodig te wees nie."</string>
<string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"luister vir waarnemings oor netwerktoestande"</string>
- <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Laat \'n program luister vir waarnemings oor netwerktoestande. Behoort nooit nodig te wees vir normale programme nie."</string>
+ <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Laat \'n app luister vir waarnemings oor netwerktoestande. Dit behoort nooit vir normale apps nodig te wees nie."</string>
<string name="permlab_setInputCalibration" msgid="932069700285223434">"verander invoertoestelkalibrasie"</string>
- <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Laat die program toe om die kalibrasieparameters van die raakskerm te wysig. Dit behoort nooit vir normale programme nodig te wees nie."</string>
+ <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Laat die app toe om die kalibrasieparameters van die raakskerm te wysig. Dit behoort nooit vir normale apps nodig te wees nie."</string>
<string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"gaan in by DRM-sertifikate"</string>
<string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"Laat \'n program toe om DRM-sertifikate op te stel en te gebruik. Behoort nooit vir normale programme nodig te wees nie."</string>
<string name="permlab_handoverStatus" msgid="7620438488137057281">"ontvang Android Straal-oordragstatus"</string>
@@ -823,7 +826,7 @@
<string name="permlab_access_notification_policy" msgid="5524112842876975537">"verkry toegang tot Moenie Steur Nie"</string>
<string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Laat die program toe om Moenie Steur Nie-opstelling te lees en skryf."</string>
<string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"begin kyk van toestemminggebruik"</string>
- <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Laat die houer toe om die toestemminggebruik vir \'n program te begin. Behoort nooit vir normale programme nodig te wees nie."</string>
+ <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Laat die houer toe om die toestemminggebruik vir \'n app te begin. Dit behoort nooit vir normale apps nodig te wees nie."</string>
<string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"begin Bekyk Toestemmingbesluite"</string>
<string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Laat die houer toe om skerm te begin om toestemmingbesluite na te gaan. Behoort nooit vir normale programme nodig te wees nie."</string>
<string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"begin Bekyk Programkenmerke"</string>
@@ -983,7 +986,7 @@
<string name="sipAddressTypeHome" msgid="5918441930656878367">"Tuis"</string>
<string name="sipAddressTypeWork" msgid="7873967986701216770">"Werk"</string>
<string name="sipAddressTypeOther" msgid="6317012577345187275">"Ander"</string>
- <string name="quick_contacts_not_available" msgid="1262709196045052223">"Geen program gekry om hierdie kontak te bekyk nie."</string>
+ <string name="quick_contacts_not_available" msgid="1262709196045052223">"Geen app gekry om hierdie kontak te bekyk nie."</string>
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Voer PIN-kode in"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Voer PUK en nuwe PIN-kode in"</string>
<string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK-kode"</string>
@@ -1101,7 +1104,7 @@
<string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nIs jy seker dat jy weg van hierdie bladsy af wil navigeer?"</string>
<string name="autofill_window_title" msgid="4379134104008111961">"Outovul met <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"stel \'n wekker"</string>
- <string name="permdesc_setAlarm" msgid="2185033720060109640">"Laat die program toe om \'n alarm in \'n geïnstalleerde wekkerprogram te stel. Sommige wekkerprogramme werk dalk nie met hierdie funksie nie."</string>
+ <string name="permdesc_setAlarm" msgid="2185033720060109640">"Laat die app toe om \'n alarm in \'n geïnstalleerde wekkerapp te stel. Sommige wekkerapps werk dalk nie met hierdie funksie nie."</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"voeg stemboodskap by"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"Laat die program toe om boodskappe by te voeg by jou stempos-inkassie."</string>
<string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> het van jou knipbord af geplak"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"oor <xliff:g id="COUNT">%d</xliff:g> u."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"oor <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"oor <xliff:g id="COUNT">%d</xliff:g> j."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuut gelede}other{# minute gelede}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# uur gelede}other{# uur gelede}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# dag gelede}other{# dae gelede}}"</string>
@@ -1247,13 +1282,13 @@
<string name="use_a_different_app" msgid="4987790276170972776">"Gebruik \'n ander program"</string>
<string name="clearDefaultHintMsg" msgid="1325866337702524936">"Vee die verstek instelling uit in Stelselinstellings &gt; Programme &gt; Afgelaai."</string>
<string name="chooseActivity" msgid="8563390197659779956">"Kies \'n handeling"</string>
- <string name="chooseUsbActivity" msgid="2096269989990986612">"Kies \'n program vir die USB-toestel"</string>
+ <string name="chooseUsbActivity" msgid="2096269989990986612">"Kies \'n app vir die USB-toestel"</string>
<string name="noApplications" msgid="1186909265235544019">"Geen programme kan hierdie handeling uitvoer nie."</string>
<string name="aerr_application" msgid="4090916809370389109">"<xliff:g id="APPLICATION">%1$s</xliff:g> het gestop"</string>
<string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> het gestop"</string>
<string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> stop aanhoudend"</string>
<string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> stop aanhoudend"</string>
- <string name="aerr_restart" msgid="2789618625210505419">"Maak program weer oop"</string>
+ <string name="aerr_restart" msgid="2789618625210505419">"Maak app weer oop"</string>
<string name="aerr_report" msgid="3095644466849299308">"Stuur terugvoer"</string>
<string name="aerr_close" msgid="3398336821267021852">"Maak toe"</string>
<string name="aerr_mute" msgid="2304972923480211376">"Demp totdat toestel herbegin"</string>
@@ -1311,7 +1346,7 @@
<string name="dump_heap_ready_notification" msgid="2302452262927390268">"<xliff:g id="PROC">%1$s</xliff:g>-hoopstorting is gereed"</string>
<string name="dump_heap_notification_detail" msgid="8431586843001054050">"Hoopstorting is ingesamel. Tik om te deel."</string>
<string name="dump_heap_title" msgid="4367128917229233901">"Deel hoopstorting?"</string>
- <string name="dump_heap_text" msgid="1692649033835719336">"Die <xliff:g id="PROC">%1$s</xliff:g>-proses het sy geheuelimiet van <xliff:g id="SIZE">%2$s</xliff:g> oorskry. \'n Hoopstorting is beskikbaar wat jy met sy ontwikkelaar kan deel. Pas op: Hierdie hoopstorting bevat dalk van jou persoonlike inligting waartoe die program toegang het."</string>
+ <string name="dump_heap_text" msgid="1692649033835719336">"Die <xliff:g id="PROC">%1$s</xliff:g>-proses het sy geheuelimiet van <xliff:g id="SIZE">%2$s</xliff:g> oorskry. \'n Hoopstorting is beskikbaar wat jy met sy ontwikkelaar kan deel. Pas op: Hierdie hoopstorting bevat dalk van jou persoonlike inligting waartoe die app toegang het."</string>
<string name="dump_heap_system_text" msgid="6805155514925350849">"Die <xliff:g id="PROC">%1$s</xliff:g>-proses het sy geheuelimiet van <xliff:g id="SIZE">%2$s</xliff:g> oorskry. \'n Hoopstorting is beskikbaar wat jy kan deel. Wees versigtig: Hierdie hoopstorting kan enige sensitiewe persoonlike inligting bevat waarby die proses kan ingaan, wat goed kan insluit wat jy getik het."</string>
<string name="dump_heap_ready_text" msgid="5849618132123045516">"\'n Hoopstorting van <xliff:g id="PROC">%1$s</xliff:g> se proses is beskikbaar vir jou om te deel. Wees versigtig: Hierdie hoopstorting kan sensitiewe persoonlike inligting bevat waarby die proses kan ingaan, wat goed kan insluit wat jy getik het."</string>
<string name="sendText" msgid="493003724401350724">"Kies \'n handeling vir teks"</string>
@@ -1496,7 +1531,7 @@
<string name="ext_media_status_missing" msgid="6520746443048867314">"Nie ingevoeg nie"</string>
<string name="activity_list_empty" msgid="4219430010716034252">"Geen passende aktiwiteite gevind nie."</string>
<string name="permlab_route_media_output" msgid="8048124531439513118">"roeteer media-uitvoer"</string>
- <string name="permdesc_route_media_output" msgid="1759683269387729675">"Laat \'n program toe om media-uitvoere na ander eksterne toestelle te roeteer."</string>
+ <string name="permdesc_route_media_output" msgid="1759683269387729675">"Laat \'n app toe om media-uitvoere na ander eksterne toestelle te roeteer."</string>
<string name="permlab_readInstallSessions" msgid="7279049337895583621">"lees installeersessies"</string>
<string name="permdesc_readInstallSessions" msgid="4012608316610763473">"Laat \'n program toe om installasiesessies te lees. Dit laat dit toe om besonderhede van aktiewe pakketinstallasies te sien."</string>
<string name="permlab_requestInstallPackages" msgid="7600020863445351154">"versoek installeerpakkette"</string>
@@ -1527,7 +1562,7 @@
<string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Toestemming versoek\nvir rekening <xliff:g id="ACCOUNT">%s</xliff:g>."</string>
<string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Toestemming versoek deur <xliff:g id="APP">%1$s</xliff:g>\nvir rekening <xliff:g id="ACCOUNT">%2$s</xliff:g>"</string>
<string name="forward_intent_to_owner" msgid="4620359037192871015">"Jy gebruik hierdie program buite jou werkprofiel"</string>
- <string name="forward_intent_to_work" msgid="3620262405636021151">"Jy gebruik tans hierdie program in jou werkprofiel"</string>
+ <string name="forward_intent_to_work" msgid="3620262405636021151">"Jy gebruik tans hierdie app in jou werkprofiel"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"Invoermetode"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Sinkroniseer"</string>
<string name="accessibility_binding_label" msgid="1974602776545801715">"Toeganklikheid"</string>
@@ -1603,7 +1638,7 @@
<string name="keyboardview_keycode_mode_change" msgid="2743735349997999020">"Modus verander"</string>
<string name="keyboardview_keycode_shift" msgid="3026509237043975573">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="168054869339091055">"Invoersleutel"</string>
- <string name="activitychooserview_choose_application" msgid="3500574466367891463">"Kies \'n program"</string>
+ <string name="activitychooserview_choose_application" msgid="3500574466367891463">"Kies \'n app"</string>
<string name="activitychooserview_choose_application_error" msgid="6937782107559241734">"Kon <xliff:g id="APPLICATION_NAME">%s</xliff:g> nie begin nie"</string>
<string name="shareactionprovider_share_with" msgid="2753089758467748982">"Deel met"</string>
<string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Deel met <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
@@ -1776,7 +1811,7 @@
<string name="guest_name" msgid="8502103277839834324">"Gas"</string>
<string name="error_message_title" msgid="4082495589294631966">"Fout"</string>
<string name="error_message_change_not_allowed" msgid="843159705042381454">"Jou administrateur laat nie hierdie verandering toe nie"</string>
- <string name="app_not_found" msgid="3429506115332341800">"Geen program gevind om hierdie handeling te hanteer nie"</string>
+ <string name="app_not_found" msgid="3429506115332341800">"Geen app gevind om hierdie handeling te hanteer nie"</string>
<string name="revoke" msgid="5526857743819590458">"Herroep"</string>
<string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
<string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> tot <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Enige kalender"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> demp sekere klanke"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Daar is \'n interne probleem met jou toestel en dit sal dalk onstabiel wees totdat jy \'n fabriekterugstelling doen."</string>
@@ -2038,11 +2074,11 @@
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou Android TV-toestel."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou tablet."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou foon."</string>
- <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Hierdie program is vir ’n ouer weergawe van Android gebou. Dit sal dalk nie behoorlik werk nie en dit sluit nie die jongste sekuriteit en privaatheidbeskermings in nie. Kyk of daar ’n opdatering is of kontak die program se ontwikkelaar."</string>
+ <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Hierdie app is vir ’n ouer weergawe van Android gebou. Dit sal dalk nie behoorlik werk nie en dit sluit nie die jongste sekuriteit en privaatheidbeskermings in nie. Kyk of daar ’n opdatering is of kontak die app se ontwikkelaar."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string>
<string name="deprecated_abi_message" msgid="6820548011196218091">"Hierdie app is nie met die jongste weergawe van Android versoenbaar nie. Kyk of daar ’n opdatering is, of kontak die app se ontwikkelaar."</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string>
- <string name="new_sms_notification_content" msgid="3197949934153460639">"Maak SMS-program oop om te bekyk"</string>
+ <string name="new_sms_notification_content" msgid="3197949934153460639">"Maak SMS-app oop om te bekyk"</string>
<string name="profile_encrypted_title" msgid="9001208667521266472">"Sommige funksies kan beperk wees"</string>
<string name="profile_encrypted_detail" msgid="5279730442756849055">"Werkprofiel is gesluit"</string>
<string name="profile_encrypted_message" msgid="1128512616293157802">"Tik om werkprofiel te ontsluit"</string>
@@ -2164,7 +2200,7 @@
<string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Tablet se battery het genoeg krag. Kenmerke is nie meer beperk nie."</string>
<string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Toestel se battery het genoeg krag. Kenmerke is nie meer beperk nie."</string>
<string name="mime_type_folder" msgid="2203536499348787650">"Vouer"</string>
- <string name="mime_type_apk" msgid="3168784749499623902">"Android-program"</string>
+ <string name="mime_type_apk" msgid="3168784749499623902">"Android-app"</string>
<string name="mime_type_generic" msgid="4606589110116560228">"Lêer"</string>
<string name="mime_type_generic_ext" msgid="9220220924380909486">"<xliff:g id="EXTENSION">%1$s</xliff:g>-lêer"</string>
<string name="mime_type_audio" msgid="4933450584432509875">"Oudio"</string>
@@ -2186,7 +2222,7 @@
<string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} + # lêer}other{{file_name} + # lêers}}"</string>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Geen mense om mee te deel is aanbeveel nie"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Programmelys"</string>
- <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Opneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel opneem."</string>
+ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Opneemtoestemming is nie aan hierdie app verleen nie, maar dit kan oudio deur hierdie USB-toestel opneem."</string>
<string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Tuis"</string>
<string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Terug"</string>
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Onlangse programme"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 225f04d53ad0..ad5bbcbb8cbe 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"የደዋይ ID ወደ ተከልክሏል ነባሪዎች።ቀጥሎ ጥሪ፡ አልተከለከለም"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"የደዋይ ID ወደ አልተከለከለም ነባሪዎች።ቀጥሎ ጥሪ፡ ተከልክሏል"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ይህ መተግበሪያ ለ16 ኪባ ተኳዃኝ አይደለም። የኤፒኬ አሰላለፍ ፍተሻ አልተሳካም። ይህ መተግበሪያ የገፅ መጠን ተኳዃኝ ሁነታን በመጠቀም ይሄዳል። ለምርጥ ተኳዃኝነት እባክዎ መተግበሪያውን በ16 ኪባ ድጋፍ እንደገና ያጠናቅሩ። ለተጨማሪ መረጃ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; የሚለውን ይመልከቱ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ይህ መተግበሪያ ለ16 ኪባ ተኳዃኝ አይደለም። የELF አሰላለፍ ፍተሻ አልተሳካም። ይህ መተግበሪያ የገፅ መጠን ተኳዃኝ ሁነታን በመጠቀም ይሄዳል። ለምርጥ ተኳዃኝነት እባክዎ መተግበሪያውን በ16 ኪባ ድጋፍ እንደገና ያጠናቅሩ። ለተጨማሪ መረጃ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; የሚለውን ይመልከቱ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ይህ መተግበሪያ ለ16 ኪባ ተኳዃኝ አይደለም። ኤፒኬ እና ELF አሰላለፍ ፍተሻዎች አልተሳኩም። ይህ መተግበሪያ የገፅ መጠን ተኳዃኝ ሁነታን በመጠቀም ይሄዳል። ለምርጥ ተኳዃኝነት እባክዎ መተግበሪያውን በ16 ኪባ ድጋፍ እንደገና ያጠናቅሩ። ለተጨማሪ መረጃ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; የሚለውን ይመልከቱ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"አገልግሎት አልቀረበም።"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"የደዋይ መታወቂያ ቅንብሮች መለወጥ አትችልም፡፡"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ውሂብ ወደ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ተቀይሯል"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"በ<xliff:g id="COUNT">%d</xliff:g> ሰ ውስጥ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"በ<xliff:g id="COUNT">%d</xliff:g> ቀ ውስጥ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"በ<xliff:g id="COUNT">%d</xliff:g> ዓ ውስጥ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{ከ# ደቂቃ በፊት}one{# ደቂቃ በፊት}other{# ደቂቃዎች በፊት}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{ከ# ሰዓት በፊት}one{ከ# ሰዓት በፊት}other{ከ# ሰዓታት በፊት}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{ከ# ቀን በፊት}one{ከ# ቀን በፊት}other{ከ# ቀናት በፊት}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"፣ "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"ከ<xliff:g id="START">%1$s</xliff:g> እስከ <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>፣ <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ማንኛውም ቀን መቁጠሪያ"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> አንዳንድ ድምጾችን እየዘጋ ነው"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ፣ የፋብሪካ ውሂብ ዳግም እስኪያስጀምሩት ድረስ ላይረጋጋ ይችላል።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 484d22047fcf..8cb64c5712f8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -75,6 +75,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"الإعداد التلقائي لمعرف المتصل هو محظور . الاتصال التالي: غير محظور"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"الإعداد التلقائي لمعرف المتصل هو غير محظور . الاتصال التالي: محظور"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"الإعداد التلقائي لمعرف المتصل هو غير محظور . الاتصال التالي: غير محظور"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عملية التأكُّد من محاذاة ملفات APK. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عملية التأكُّد من محاذاة ملفات ELF. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عمليات التأكُّد من محاذاة ملفات APK وELF. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"الخدمة غير متوفرة."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"تم تبديل البيانات إلى <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1159,6 +1162,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"خلال <xliff:g id="COUNT">%d</xliff:g> ساعة"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"خلال <xliff:g id="COUNT">%d</xliff:g> يوم"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"خلال <xliff:g id="COUNT">%d</xliff:g> سنة"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{قبل دقيقة واحدة}zero{قبل # دقيقة}two{قبل دقيقتين}few{قبل # دقائق}many{قبل # دقيقة}other{قبل # دقيقة}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{قبل ساعة واحدة}zero{قبل # ساعة}two{قبل ساعتين}few{قبل # ساعات}many{قبل # ساعة}other{قبل # ساعة}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{قبل يوم واحد}zero{قبل # يوم}two{قبل يومين}few{قبل # أيام}many{قبل # يومًا}other{قبل # يوم}}"</string>
@@ -1951,6 +1986,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"من <xliff:g id="START">%1$s</xliff:g> إلى <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"من <xliff:g id="START">%1$s</xliff:g> إلى <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"أي تقويم"</string>
<string name="muted_by" msgid="91464083490094950">"يعمل <xliff:g id="THIRD_PARTY">%1$s</xliff:g> على كتم بعض الأصوات."</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط على الإعدادات الأصلية."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index fdddc70e56d3..4214ebee2e47 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"কলাৰ আইডি সীমিত কৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হৈছে"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"এই এপ্‌টো ১৬ কেবিৰ সমিল নহয়। APKৰ শাৰীবিন্যাস পৰীক্ষা বিফল হৈছে। এই এপ্‌টো পৃষ্ঠাৰ আকাৰৰ সমিল ম’ড ব্যৱহাৰ কৰি চলোৱা হ’ব। উত্তম সমিলতাৰ বাবে, অনুগ্ৰহ কৰি এপ্লিকেশ্বনটো ১৬ কেবিৰ সমৰ্থনৰ সৈতে পুনৰায় কম্পাইল কৰক। অধিক তথ্যৰ বাবে, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;লৈ যাওক"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"এই এপ্‌টো ১৬ কেবিৰ সমিল নহয়। ELFৰ শাৰীবিন্যাস পৰীক্ষা বিফল হৈছে। এই এপ্‌টো পৃষ্ঠাৰ আকাৰৰ সমিল ম’ড ব্যৱহাৰ কৰি চলোৱা হ’ব। উত্তম সমিলতাৰ বাবে, অনুগ্ৰহ কৰি এপ্লিকেশ্বনটো ১৬ কেবিৰ সমৰ্থনৰ সৈতে পুনৰায় কম্পাইল কৰক। অধিক তথ্যৰ বাবে, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;লৈ যাওক"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"এই এপ্‌টো ১৬ কেবিৰ সমিল নহয়। APK আৰু ELFৰ শাৰীবিন্যাস পৰীক্ষা বিফল হৈছে। এই এপ্‌টো পৃষ্ঠাৰ আকাৰৰ সমিল ম’ড ব্যৱহাৰ কৰি চলোৱা হ’ব। উত্তম সমিলতাৰ বাবে, অনুগ্ৰহ কৰি এপ্লিকেশ্বনটো ১৬ কেবিৰ সমৰ্থনৰ সৈতে পুনৰায় কম্পাইল কৰক। অধিক তথ্যৰ বাবে, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;লৈ যাওক"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"সুবিধা যোগান ধৰা হোৱা নাই।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"আপুনি কলাৰ আইডি ছেটিং সলনি কৰিব নোৱাৰে।"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ডেটা <xliff:g id="CARRIERDISPLAY">%s</xliff:g>লৈ সলনি কৰা হৈছে"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> ঘণ্টাত"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>দিনত"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> বছৰত"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# মিনিট পূৰ্বে}one{# মিনিট পূৰ্বে}other{# মিনিট পূৰ্বে}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ঘণ্টা পূৰ্বে}one{# ঘণ্টা পূৰ্বে}other{# ঘণ্টা পূৰ্বে}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# দিন পূর্বে}one{# দিন পূৰ্বে}other{# দিন পূৰ্বে}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ৰ পৰা <xliff:g id="END">%2$s</xliff:g>লৈ"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"যিকোনো কেলেণ্ডাৰ"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>এ কিছুমান ধ্বনি মিউট কৰি আছে"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে আৰু আপুনি ফেক্টৰী ডেটা ৰিছেট নকৰালৈকে ই সুস্থিৰভাৱে কাম নকৰিব পাৰে।"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 003c0dbc6f26..d4389dc0a467 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağandır"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Bu tətbiq 16 KB ilə uyğunlaşmır. APK düzləndirmə yoxlaması alınmadı. Bu tətbiq səhifə ölçüsünə uyğun rejimdən istifadə edilməklə işlədiləcək. Ən yaxşı uyğunluq üçün tətbiqi 16 KB dəstəyi ilə yenidən tərtib edin. Ətraflı məlumat üçün nəzər salın: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Bu tətbiq 16 KB ilə uyğunlaşmır. ELF düzləndirmə yoxlaması alınmadı. Bu tətbiq səhifə ölçüsünə uyğun rejimdən istifadə edilməklə işlədiləcək. Ən yaxşı uyğunluq üçün tətbiqi 16 KB dəstəyi ilə yenidən tərtib edin. Ətraflı məlumat üçün nəzər salın: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Bu tətbiq 16 KB ilə uyğunlaşmır. APK və ELF düzləndirmə yoxlamaları uğursuz oldu. Bu tətbiq səhifə ölçüsünə uyğun rejimdən istifadə edilməklə işlədiləcək. Ən yaxşı uyğunluq üçün tətbiqi 16 KB dəstəyi ilə yenidən tərtib edin. Ətraflı məlumat üçün nəzər salın: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Xidmət təmin edilməyib."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Çağrı kimliyi ayarını dəyişə bilməzsiniz."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Data <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoruna keçirilib"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> s ərzində"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> g ərzində"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> il ərzində"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# dəqiqə əvvəl}other{# dəqiqə əvvəl}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# saat əvvəl}other{# saat əvvəl}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# gün əvvəl}other{# gün əvvəl}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"İstənilən təqvim"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bəzi səsləri səssiz rejimə salır"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızın daxili problemi var və istehsalçı sıfırlanması olmayana qədər qeyri-stabil ola bilər."</string>
@@ -2418,7 +2454,7 @@
<string name="accessibility_label_private_profile" msgid="1436459319135548969">"Məxfi sahə"</string>
<string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string>
<string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Kommunal"</string>
- <string name="private_space_biometric_prompt_title" msgid="5777592909271728154">"Şəxsi sahə"</string>
+ <string name="private_space_biometric_prompt_title" msgid="5777592909271728154">"Məxfi sahə"</string>
<string name="redacted_notification_message" msgid="1520587845842228816">"Həssas bildiriş kontenti gizlədildi"</string>
<string name="redacted_notification_action_title" msgid="6942924973335920935"></string>
<string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Tətbiq kontenti güvənlik məsələlərinə görə ekran paylaşımından gizlədildi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 5a848bafc7eb..911eede993cd 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID pozivaoca je podrazumevano ograničen. Sledeći poziv: Nije ograničen."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: ograničen."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ova aplikacija nije kompatibilna sa veličinom stranice od 16 kB. Provera usklađenosti APK-a nije uspela. Ova aplikacija će raditi pomoću režima kompatibilnog sa veličinom stranice. Da biste obezbedili najbolju kompatibilnost, ponovo kompajlirajte aplikaciju sa podrškom za veličinu stranice od 16 kB. Više informacija potražite na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ova aplikacija nije kompatibilna sa veličinom stranice od 16 kB. Provera usklađenosti ELF-a nije uspela. Ova aplikacija će raditi pomoću režima kompatibilnog sa veličinom stranice. Da biste obezbedili najbolju kompatibilnost, ponovo kompajlirajte aplikaciju sa podrškom za veličinu stranice od 16 kB. Više informacija potražite na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ova aplikacija nije kompatibilna sa veličinom stranice od 16 kB. Provere usklađenosti APK-a i ELF-a nisu uspele. Ova aplikacija će raditi pomoću režima kompatibilnog sa veličinom stranice. Da biste obezbedili najbolju kompatibilnost, ponovo kompajlirajte aplikaciju sa podrškom za veličinu stranice od 16 kB. Više informacija potražite na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Mobilni podaci su prebačeni na operatera <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> s"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> god"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Pre # minut}one{Pre # minut}few{Pre # minuta}other{Pre # minuta}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Pre # sat}one{Pre # sat}few{Pre # sata}other{Pre # sati}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Pre # dan}one{Pre # dan}few{Pre # dana}other{Pre # dana}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvuke"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Došlo je do internog problema u vezi sa uređajem i možda će biti nestabilan dok ne obavite resetovanje na fabrička podešavanja."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index ce98a5ff7473..0165929df175 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ідэнтыфікатар АВН па змаўчанні абмежаваны. Наступны выклік: не абмежавана"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Па змаўчанні ідэнтыфікатар АВН не абмежаваны. Наступны выклік: абмежаваны"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Гэта праграма несумяшчальная з памерам 16 КБ. Не ўдалося праверыць выраўноўванне APK. Гэта праграма будзе працаваць у рэжыме, сумяшчальным з памерам старонкі. Для найлепшай сумяшчальнасці выканайце паўторнае кампіляванне праграмы з падтрымкай 16 КБ. Дадатковую інфармацыю можна знайсці на старонцы &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Гэта праграма несумяшчальная з памерам 16 КБ. Не ўдалося праверыць выраўноўванне ELF. Гэта праграма будзе працаваць у рэжыме, сумяшчальным з памерам старонкі. Для найлепшай сумяшчальнасці выканайце паўторнае кампіляванне праграмы з падтрымкай 16 КБ. Дадатковую інфармацыю можна знайсці на старонцы &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Гэта праграма несумяшчальная з памерам 16 КБ. Не ўдалося праверыць выраўноўванне APK і ELF. Гэта праграма будзе працаваць у рэжыме, сумяшчальным з памерам старонкі. Для найлепшай сумяшчальнасці выканайце паўторнае кампіляванне праграмы з падтрымкай 16 КБ. Дадатковую інфармацыю можна знайсці на старонцы &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Мабільны трафік пераключаны на аператара \"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>\""</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"праз <xliff:g id="COUNT">%d</xliff:g> гадз"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"праз <xliff:g id="COUNT">%d</xliff:g> сут"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"праз <xliff:g id="COUNT">%d</xliff:g> г."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# хвіліну таму}one{# хвіліну таму}few{# хвіліны таму}many{# хвілін таму}other{# хвіліны таму}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# гадзіну таму}one{# гадзіну таму}few{# гадзіны таму}many{# гадзін таму}other{# гадзіны таму}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# дзень таму}one{# дзень таму}few{# дні таму}many{# дзён таму}other{# дня таму}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Любы каляндар"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> выключае некаторыя гукі"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"На вашай прыладзе ўзнікла ўнутраная праблема, і яна можа працаваць нестабільна, пакуль вы не зробіце скід да заводскіх налад."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index aad5d48f80cd..1b6867557302 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Стандартната идентификация на повикванията е „забранено“. За следващото обаждане тя е разрешена."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е забранена."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е разрешена."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Това приложение не е съвместимо със страници с размер 16 KB. Проверката за съответствие с APK файла не бе успешна. Това приложение ще се изпълнява в режим на съвместимост с размера на страницата. За най-добра съвместимост компилирайте отново приложението така, че да поддържа страници с размер 16 KB. За повече информация вижте &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Това приложение не е съвместимо със страници с размер 16 KB. Проверката за съответствие с ELF файла не бе успешна. Това приложение ще се изпълнява в режим на съвместимост с размера на страницата. За най-добра съвместимост компилирайте отново приложението така, че да поддържа страници с размер 16 KB. За повече информация вижте &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Това приложение не е съвместимо със страници с размер 16 KB. Проверките за съответствие с APK и ELF файловете не бяха успешни. Това приложение ще се изпълнява в режим на съвместимост с размера на страницата. За най-добра съвместимост компилирайте отново приложението така, че да поддържа страници с размер 16 KB. За повече информация вижте &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е обезпечена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не можете да променяте настройката за идентификация на обажданията."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Преминахте към мобилни данни от <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"след <xliff:g id="COUNT">%d</xliff:g> ч"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"след <xliff:g id="COUNT">%d</xliff:g> д"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"след <xliff:g id="COUNT">%d</xliff:g> г."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{преди # минута}other{преди # минути}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{преди # час}other{преди # часа}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{преди # ден}other{преди # дни}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"От <xliff:g id="START">%1$s</xliff:g> до <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Всички календари"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> заглушава някои звуци"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Възникна вътрешен проблем с устройството ви. То може да е нестабилно, докато не възстановите фабричните настройки."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index d52f6e91f45b..3b832e0b4299 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"এই অ্যাপ ১৬ কেবির সাথে মানানসই নয়। APK অ্যালাইনমেন্ট চেক করা যায়নি। এই অ্যাপ পৃষ্ঠার সাইজের সাথে মানানসই মোড ব্যবহার করে চলবে। সবচেয়ে ভালো মানানসই হিসেবে কাজ করার জন্য অ্যাপ্লিকেশনকে ১৬ কেবি সাপোর্টের সাথে আবার কম্পাইল করুন। আরও তথ্যের জন্য, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; দেখুন"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"এই অ্যাপ ১৬ কেবির সাথে মানানসই নয়। ELF অ্যালাইনমেন্ট চেক করা যায়নি। এই অ্যাপ পৃষ্ঠার সাইজের সাথে মানানসই মোড ব্যবহার করে চলবে। সবচেয়ে ভালো মানানসই হিসেবে কাজ করার জন্য অ্যাপ্লিকেশনকে ১৬ কেবি সাপোর্টের সাথে আবার কম্পাইল করুন। আরও তথ্যের জন্য, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; দেখুন"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"এই অ্যাপ ১৬ কেবির সাথে মানানসই নয়। APK ও ELF অ্যালাইনমেন্ট পরীক্ষা করা যায়নি। এই অ্যাপ পৃষ্ঠার সাইজের সাথে মানানসই মোড ব্যবহার করে চলবে। সবচেয়ে ভালো মানানসই হিসেবে কাজ করার জন্য অ্যাপ্লিকেশনকে ১৬ কেবি সাপোর্টের সাথে আবার কম্পাইল করুন। আরও তথ্যের জন্য, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; দেখুন"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"পরিষেবা প্রস্তুত নয়৷"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"আপনি কলার আইডি এর সেটিংস পরিবর্তন করতে পারবেন না৷"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>-এর ডেটা ব্যবহার করা হয়েছে"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>ঘণ্টার মধ্যে"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>দিনের মধ্যে"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>বছরের মধ্যে"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# মিনিট আগে}one{# মিনিট আগে}other{# মিনিট আগে}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ঘণ্টা আগে}one{# ঘণ্টা আগে}other{# ঘণ্টা আগে}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# দিন আগে}one{# দিন আগে}other{# দিন আগে}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> থেকে <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"যেকোনও ক্যালেন্ডার"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> কিছু সাউন্ডকে মিউট করে দিচ্ছে"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে, এবং আপনি যতক্ষণ না পর্যন্ত এটিকে ফ্যাক্টরি ডেটা রিসেট করছেন ততক্ষণ এটি ঠিকভাবে কাজ নাও করতে পারে৷"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b74b13966405..64ded2d17e8f 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Prikaz ID-a pozivaoca u zadanim postavkama zabranjen. Sljedeći poziv: nije zabranjen"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: zabranjen"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: nije zabranjen"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 kB. Provjera poravnanja APK-a nije uspjela. Aplikacija će raditi pomoću načina rada za kompatibilnost s veličinom stranice. Za najbolju kompatibilnost ponovo kompajlirajte aplikaciju s podrškom za veličinu od 16 kB. Za više informacija pogledajte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 kB. Provjera poravnanja ELF-a nije uspjela. Aplikacija će raditi pomoću načina rada za kompatibilnost s veličinom stranice. Za najbolju kompatibilnost ponovo kompajlirajte aplikaciju s podrškom za veličinu od 16 kB. Za više informacija pogledajte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 kB. Provjere poravnanja APK-a i ELF-a nisu uspjele. Aplikacija će raditi pomoću načina rada za kompatibilnost s veličinom stranice. Za najbolju kompatibilnost ponovo kompajlirajte aplikaciju s podrškom za veličinu od 16 kB. Za više informacija pogledajte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Uslugu nije moguće koristiti."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavke ID-a pozivaoca."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Prijenos podataka usmjeravanjem na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> g"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Prije # min}one{Prije # min}few{Prije # min}other{Prije # min}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Prije # h}one{Prije # h}few{Prije # h}other{Prije # h}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Prije # dan}one{Prije # dan}few{Prije # dana}other{Prije # dana}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Postoji problem u vašem uređaju i može biti nestabilan dok ga ne vratite na fabričke postavke."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 7500122e6d94..d11941af34d5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"El valor predeterminat de l\'identificador de trucada és restringit. Trucada següent: no restringit"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: restringit"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: no restringit"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Aquesta aplicació no és compatible amb 16 kB. No s\'ha pogut comprovar l\'alineació d\'APK. Aquesta aplicació s\'executarà en el mode compatible amb la mida de la pàgina. Per obtenir la millor compatibilitat, torna a compilar l\'aplicació amb compatibilitat de 16 kB. Per obtenir més informació, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Aquesta aplicació no és compatible amb 16 kB. No s\'ha pogut comprovar l\'alineació d\'ELF. Aquesta aplicació s\'executarà en el mode compatible amb la mida de la pàgina. Per obtenir la millor compatibilitat, torna a compilar l\'aplicació amb compatibilitat de 16 kB. Per obtenir més informació, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Aquesta aplicació no és compatible amb 16 kB. No s\'ha pogut comprovar l\'alineació d\'APK i ELF. Aquesta aplicació s\'executarà en el mode compatible amb la mida de la pàgina. Per obtenir la millor compatibilitat, torna a compilar l\'aplicació amb compatibilitat de 16 kB. Per obtenir més informació, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"No s\'ha proveït el servei."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No pots canviar la configuració de l\'identificador de trucada."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Les dades s\'han canviat a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Fa # minut}many{Fa # minuts}other{Fa # minuts}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Fa # hora}many{Fa # hores}other{Fa # hores}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Fa # dia}many{Fa # dies}other{Fa # dies}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsevol calendari"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> està silenciant alguns sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"S\'ha produït un error intern al dispositiu i és possible que funcioni de manera inestable fins que restableixis les dades de fàbrica."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 277d359e5fd0..50c3e6a62787 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ve výchozím nastavení je funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Omezeno"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Tato aplikace není kompatibilní s 16KB režimem. Kontrola zarovnání souboru APK selhala. Tato aplikace poběží v režimu kompatibilním s velikostmi stránek. Pro optimální kompatibilitu ji překompilujte s podporou 16KB režimu. Další informace najdete na stránce &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Tato aplikace není kompatibilní s 16KB režimem. Kontrola zarovnání souboru ELF selhala. Tato aplikace poběží v režimu kompatibilním s velikostmi stránek. Pro optimální kompatibilitu ji překompilujte s podporou 16KB režimu. Další informace najdete na stránce &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Tato aplikace není kompatibilní s 16KB režimem. Kontroly zarovnání souborů APK a ELF selhaly. Tato aplikace poběží v režimu kompatibilním s velikostmi stránek. Pro optimální kompatibilitu ji překompilujte s podporou 16KB režimu. Další informace najdete na stránce &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba není zřízena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nastavení ID volajícího nesmíte měnit."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Datové připojení bylo přepnuto na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> r"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{před # minutou}few{před # minutami}many{před # minuty}other{před # minutami}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{před # hodinou}few{před # hodinami}many{před # hodiny}other{před # hodinami}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Před # dnem}few{před # dny}many{před # dne}other{před # dny}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"V libovolném kalendáři"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypíná určité zvuky"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"V zařízení došlo k internímu problému. Dokud neprovedete obnovení továrních dat, může být nestabilní."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 9db6076ee6fa..9bd5f70d6e6f 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Standarder for opkalds-id til begrænset. Næste opkald: Ikke begrænset"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Begrænset"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Ikke begrænset"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Denne app er ikke kompatibel med sidestørrelser på 16 kB. Tjek af APK-afstemning mislykkedes. Denne app køres i kompatibilitetstilstand for sidestørrelse. Kompiler appen igen, så den understøtter 16 kB, for at opnå bedst mulig kompatibilitet. Få flere oplysninger på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Denne app er ikke kompatibel med sidestørrelser på 16 kB. Tjek af ELF-afstemning mislykkedes. Denne app køres i kompatibilitetstilstand for sidestørrelse. Kompiler appen igen, så den understøtter 16 kB, for at opnå bedst mulig kompatibilitet. Få flere oplysninger på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Denne app er ikke kompatibel med sidestørrelser på 16 kB. Tjek af APK- og ELF-afstemning mislykkedes. Denne app køres i kompatibilitetstilstand for sidestørrelse. Kompiler appen igen, så den understøtter 16 kB, for at opnå bedst mulig kompatibilitet. Få flere oplysninger på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjenesten provisioneres ikke."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke ændre indstillingen for opkalds-id\'et."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Der blev skiftet til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-data"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"om <xliff:g id="COUNT">%d</xliff:g> t."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"om <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"om <xliff:g id="COUNT">%d</xliff:g> år"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{For # minut siden}one{For # minut siden}other{For # minutter siden}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# time siden}one{# time siden}other{# timer siden}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{1 dag siden}one{1 dag siden}other{# dag siden}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> kl. <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alle kalendere"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår nogle lyde fra"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Der er et internt problem med enheden, og den vil muligvis være ustabil, indtil du gendanner fabriksdataene."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 9561b3f8a3c5..06cadd54c07c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Nicht beschränkt"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Beschränkt"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Diese App ist nicht mit 16 KB kompatibel. Die APK-Abgleichsprüfung ist fehlgeschlagen. Diese App wird im mit der Seitengröße kompatiblen Modus ausgeführt. Für eine optimale Kompatibilität rekompiliere die App, sodass sie 16 KB unterstützt. Weitere Informationen findest du unter &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Diese App ist nicht mit 16 KB kompatibel. Die ELF-Abgleichsprüfung ist fehlgeschlagen. Diese App wird im mit der Seitengröße kompatiblen Modus ausgeführt. Für eine optimale Kompatibilität rekompiliere die App, sodass sie 16 KB unterstützt. Weitere Informationen findest du unter &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Diese App ist nicht mit 16 KB kompatibel. APK- und ELF-Abgleichsprüfungen sind fehlgeschlagen. Diese App wird im mit der Seitengröße kompatiblen Modus ausgeführt. Für eine optimale Kompatibilität rekompiliere die App, sodass sie 16 KB unterstützt. Weitere Informationen findest du unter &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Dienst nicht eingerichtet."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Du kannst die Einstellung für die Anrufer-ID nicht ändern."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Mobile Daten wurden auf <xliff:g id="CARRIERDISPLAY">%s</xliff:g> umgestellt"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g> Std."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g> T"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> J"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Vor # Minute}other{Vor # Minuten}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Vor # Stunde}other{Vor # Stunden}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Vor # Tag}other{Vor # Tagen}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> bis <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alle Kalender"</string>
<string name="muted_by" msgid="91464083490094950">"Einige Töne werden von <xliff:g id="THIRD_PARTY">%1$s</xliff:g> stummgeschaltet"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Es liegt ein internes Problem mit deinem Gerät vor. Möglicherweise verhält es sich instabil, bis du es auf die Werkseinstellungen zurücksetzt."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 551fe701e7dd..b4b70d8545ef 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Περιορισμένη."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Αυτή η εφαρμογή δεν είναι συμβατή για 16 KB. Ο έλεγχος ευθυγράμμισης APK απέτυχε. Αυτή η εφαρμογή θα εκτελεστεί χρησιμοποιώντας τη λειτουργία συμβατότητας μεγέθους σελίδας. Για τη βέλτιστη συμβατότητα, επαναμεταγλωττίστε την εφαρμογή με υποστήριξη 16 KB. Για περισσότερες πληροφορίες, ανατρέξτε στη διεύθυνση &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Αυτή η εφαρμογή δεν είναι συμβατή για 16 KB. Ο έλεγχος ευθυγράμμισης ELF απέτυχε. Αυτή η εφαρμογή θα εκτελεστεί χρησιμοποιώντας τη λειτουργία συμβατότητας μεγέθους σελίδας. Για τη βέλτιστη συμβατότητα, επαναμεταγλωττίστε την εφαρμογή με υποστήριξη 16 KB. Για περισσότερες πληροφορίες, ανατρέξτε στη διεύθυνση &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Αυτή η εφαρμογή δεν είναι συμβατή για 16 KB. Οι έλεγχοι ευθυγράμμισης APK και ELF απέτυχαν. Αυτή η εφαρμογή θα εκτελεστεί χρησιμοποιώντας τη λειτουργία συμβατότητας μεγέθους σελίδας. Για τη βέλτιστη συμβατότητα, επαναμεταγλωττίστε την εφαρμογή με υποστήριξη 16 KB. Για περισσότερες πληροφορίες, ανατρέξτε στη διεύθυνση &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Η υπηρεσία δεν προβλέπεται."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Έγινε εναλλαγή των δεδομένων σε <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"σε <xliff:g id="COUNT">%d</xliff:g>ώ."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"σε <xliff:g id="COUNT">%d</xliff:g>η."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"σε <xliff:g id="COUNT">%d</xliff:g>έτ."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# λεπτό πριν}other{Πριν από # λεπτά}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Πριν από # ώρα}other{Πριν από # ώρες}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Πριν από # ημέρα}other{Πριν από # ημέρες}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> έως <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Οποιοδήποτε ημερολόγιο"</string>
<string name="muted_by" msgid="91464083490094950">"Το τρίτο μέρος <xliff:g id="THIRD_PARTY">%1$s</xliff:g> θέτει ορισμένους ήχους σε σίγαση"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας και ενδέχεται να είναι ασταθής μέχρι την επαναφορά των εργοστασιακών ρυθμίσεων."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 5cedb1fa6655..58037eb83069 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"This app isn\'t 16 KB compatible. APK alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"This app isn\'t 16 KB compatible. ELF alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"This app isn\'t 16 KB compatible. APK and ELF alignment checks failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index b09a79a63039..baa54bbf9a8e 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"This app isn’t 16 KB compatible. APK alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"This app isn’t 16 KB compatible. ELF alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"This app isn’t 16 KB compatible. APK and ELF alignment checks failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g>y"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 4e728ff0f814..43b6ba65ff35 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"This app isn\'t 16 KB compatible. APK alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"This app isn\'t 16 KB compatible. ELF alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"This app isn\'t 16 KB compatible. APK and ELF alignment checks failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 578e4795f2b4..1a0f8e42d341 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"This app isn\'t 16 KB compatible. APK alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"This app isn\'t 16 KB compatible. ELF alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"This app isn\'t 16 KB compatible. APK and ELF alignment checks failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index da3c4c4c27eb..3c739206ec7c 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"El Identificador de llamadas está predeterminado en restringido. Llamada siguiente: no restringido"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"El identificador de llamadas está predeterminado en no restringido. Llamada siguiente: restringida"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El Identificador de llamadas está predeterminado en no restringido. Llamada siguiente: no restringido"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Esta app no es compatible con 16 KB. No se pudo verificar la alineación del APK. Esta app se ejecutará con el modo de compatibilidad de tamaño de página. Para obtener mejores resultados, vuelve a compilar la aplicación con la compatibilidad de 16 KB. Para obtener más información, consulta el siguiente vínculo: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Esta app no es compatible con 16 KB. No se pudo verificar la alineación de ELF. Esta app se ejecutará con el modo de compatibilidad de tamaño de página. Para obtener mejores resultados, vuelve a compilar la aplicación con la compatibilidad de 16 KB. Para obtener más información, consulta el siguiente vínculo: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Esta app no es compatible con 16 KB. No se pudieron verificar las alineaciones de APK y ELF. Esta app se ejecutará con el modo de compatibilidad de tamaño de página. Para obtener mejores resultados, vuelve a compilar la aplicación con la compatibilidad de 16 KB. Para obtener más información, consulta el siguiente vínculo: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servicio no suministrado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No puedes cambiar la configuración del identificador de llamadas."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Se cambiaron los datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"en <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"en <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"en <xliff:g id="COUNT">%d</xliff:g> años"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Hace # minuto}many{Hace # minutos}other{Hace # minutos}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Hace # hora}many{Hace # de horas}other{Hace # horas}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Hace # día}many{Hace # de días}other{Hace # días}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Cualquier calendario"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Existe un problema interno con el dispositivo, de modo que el dispositivo puede estar inestable hasta que restablezcas la configuración de fábrica."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 37bf70e9bbd1..a81af9f3acd2 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"La identificación del emisor presenta el valor predeterminado de restringido. Siguiente llamada: No restringido"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"La la identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: Restringido"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"La identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Esta aplicación no es compatible con el modo de 16 KB. No se ha podido comprobar la alineación del APK. Esta aplicación se ejecutará en el modo compatible con el tamaño de página. Para obtener la mejor compatibilidad, vuelve a compilar la aplicación con compatibilidad de 16 KB. Para obtener más información, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Esta aplicación no es compatible con el modo de 16 KB. No se ha podido comprobar la alineación de ELF. Esta aplicación se ejecutará en el modo compatible con el tamaño de página. Para obtener la mejor compatibilidad, vuelve a compilar la aplicación con compatibilidad de 16 KB. Para obtener más información, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Esta aplicación no es compatible con el modo de 16 KB. No se han podido comprobar las alineaciones de APK y ELF. Esta aplicación se ejecutará en el modo compatible con el tamaño de página. Para obtener la mejor compatibilidad, vuelve a compilar la aplicación con compatibilidad de 16 KB. Para obtener más información, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"El servicio no se suministra."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"No puedes modificar la identificación de emisor."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Datos cambiados a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"en <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"en <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"en <xliff:g id="COUNT">%d</xliff:g>a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Hace # minuto}many{Hace # minutos}other{Hace # minutos}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Hace # hora}many{Hace # horas}other{Hace # horas}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Hace # día}many{Hace # días}other{Hace # días}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Cualquier calendario"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Se ha producido un problema interno en el dispositivo y es posible que este no sea estable hasta que restablezcas el estado de fábrica."</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0d79e8aa0333..c5cadcf594b5 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Helistaja ID vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Vaikimisi pole helistaja ID piiratud. Järgmine kõne: piiratud"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Helistaja ID pole vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"See rakendus ei ole suurusega 16 kB ühilduv. APK joonduse kontroll ebaõnnestus. Seda rakendust käitatakse lehesuurusega ühilduvas režiimis. Parima ühilduvuse tagamiseks kompileerige rakendus 16 kB toega uuesti. Lisateave: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"See rakendus ei ole suurusega 16 kB ühilduv. ELF-i joonduse kontroll ebaõnnestus. Seda rakendust käitatakse lehesuurusega ühilduvas režiimis. Parima ühilduvuse tagamiseks kompileerige rakendus 16 kB toega uuesti. Lisateave: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"See rakendus ei ole suurusega 16 kB ühilduv. APK ja ELF-i joonduse kontroll ebaõnnestus. Seda rakendust käitatakse lehesuurusega ühilduvas režiimis. Parima ühilduvuse tagamiseks kompileerige rakendus 16 kB toega uuesti. Lisateave: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Teenus pole ette valmistatud."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Helistaja ID seadet ei saa muuta."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiilne andmeside lülitati operaatorile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> h pärast"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> p pärast"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> a pärast"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minut tagasi}other{# minutit tagasi}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# tund tagasi}other{# tundi tagasi}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# päev tagasi}other{# päeva tagasi}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> kuni <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Mis tahes kalender"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vaigistab teatud helid"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Seadmes ilmnes sisemine probleem ja seade võib olla ebastabiilne seni, kuni lähtestate seadme tehase andmetele."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index b96f9eb97c21..a0b2cc311214 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Deitzailearen identitatea adierazteko zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenik gabe."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenekin."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Aplikazio hau ez da bateragarria 16 KBko bertsioarekin. Ezin izan da egiaztatu APKren lerrokatzea. Orriaren tamainarekin bateragarria den modua erabilita exekutatuko da aplikazioa. Emaitza onenak lortzeko, konpilatu aplikazioa berriro 16 KBko bertsioarekin bateragarria izan dadin. Informazio gehiago lortzeko, joan hona: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Aplikazio hau ez da bateragarria 16 KBko bertsioarekin. Ezin izan da egiaztatu ELFaren lerrokatzea. Orriaren tamainarekin bateragarria den modua erabilita exekutatuko da aplikazioa. Emaitza onenak lortzeko, konpilatu aplikazioa berriro 16 KBko bertsioarekin bateragarria izan dadin. Informazio gehiago lortzeko, joan hona: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Aplikazio hau ez da bateragarria 16 KBko bertsioarekin. Ezin izan da egiaztatu APK eta ELF arteko lerrokatzea. Orriaren tamainarekin bateragarria den modua erabilita exekutatuko da aplikazioa. Emaitza onenak lortzeko, konpilatu aplikazioa berriro 16 KBko bertsioarekin bateragarria izan dadin. Informazio gehiago lortzeko, joan hona: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> operadorearen datu-konexiora aldatu zara"</string>
@@ -421,8 +424,8 @@
<string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Aplikazioak datuak erabil litzake atzeko planoan eta horrek datu-erabilera areago lezake."</string>
<string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"antolatu ekintzak une zehatzetan gerta daitezen"</string>
<string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"Aplikazio honek ekintzak programa ditzake etorkizunean egin daitezen. Horrek esan nahi du gailua aktiboki erabiltzen ari ez zarenean ere exekuta daitekeela aplikazioa."</string>
- <string name="permlab_use_exact_alarm" msgid="348045139777131552">"antolatu alarmak edo gertaera-abisuak"</string>
- <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Aplikazioak hainbat ekintza programa ditzake; adibidez, alarmak eta abisuak, etorkizuneko une batean jakinarazpen bat jaso dezazun."</string>
+ <string name="permlab_use_exact_alarm" msgid="348045139777131552">"antolatu alarmak edo gertaera-gogorarazpenak"</string>
+ <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Aplikazioak hainbat ekintza programa ditzake; adibidez, alarmak eta gogorarazpenak, etorkizuneko une batean jakinarazpen bat jaso dezazun."</string>
<string name="permlab_persistentActivity" msgid="464970041740567970">"izan aplikazioa beti abian"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Bere zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta tableta motel daiteke."</string>
<string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Bere zati batzuk memorian modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Ondorioz, beste aplikazioek memoria gutxiago izan lezakete erabilgarri, eta Android TV gailuak motelago funtziona lezake."</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> h barru"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> eg. barru"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> ur. barru"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Duela # minutu}other{Duela # minutu}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Duela # ordu}other{Duela # ordu}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Duela # egun}other{Duela # egun}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> (<xliff:g id="TIMES">%2$s</xliff:g>)"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Edozein egutegi"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> soinu batzuk isilarazten ari da"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Barneko arazo bat dago zure gailuan eta agian ezegonkor egongo da jatorrizko datuak berrezartzen dituzun arte."</string>
@@ -2438,7 +2474,7 @@
<string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Satelite bidezko SOS komunikazioa ez da bateragarria"</string>
<string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Satelite bidezko SOS komunikazioa ez da bateragarria gailu honekin"</string>
<string name="satellite_sos_not_provisioned_notification_title" msgid="8564738683795406715">"Satelite bidezko SOS komunikazioa ez dago konfiguratuta"</string>
- <string name="satellite_sos_not_provisioned_notification_summary" msgid="3127320958911180629">"Ziurtatu Internetera konektatuta zaudela eta saiatu konfiguratzen berriro"</string>
+ <string name="satellite_sos_not_provisioned_notification_summary" msgid="3127320958911180629">"Ziurtatu Internetera konektatuta zaudela eta saiatu berriro konfiguratzen"</string>
<string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"Satelite bidezko SOS komunikazioa ez dago erabilgarri"</string>
<string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"Satelite bidezko SOS komunikazioa ez dago erabilgarri herrialde edo lurralde honetan"</string>
<string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"Satelite bidezko SOS komunikazioa ez dago konfiguratuta"</string>
@@ -2450,7 +2486,7 @@
<string name="satellite_messaging_not_supported_notification_title" msgid="8202139632766878610">"Satelite bidezko mezularitza ez da bateragarria"</string>
<string name="satellite_messaging_not_supported_notification_summary" msgid="61629858627638545">"Satelite bidezko mezularitza ez da bateragarria gailu honekin"</string>
<string name="satellite_messaging_not_provisioned_notification_title" msgid="961909101918169727">"Satelite bidezko mezularitza ez dago konfiguratuta"</string>
- <string name="satellite_messaging_not_provisioned_notification_summary" msgid="1060961852174442155">"Ziurtatu Internetera konektatuta zaudela eta saiatu konfiguratzen berriro"</string>
+ <string name="satellite_messaging_not_provisioned_notification_summary" msgid="1060961852174442155">"Ziurtatu Internetera konektatuta zaudela eta saiatu berriro konfiguratzen"</string>
<string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"Satelite bidezko mezularitza ez dago erabilgarri"</string>
<string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"Satelite bidezko mezularitza ez dago erabilgarri herrialde edo lurralde honetan"</string>
<string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"Satelite bidezko mezularitza ez dago konfiguratuta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 7627ff37d2bc..4d3f1a3651d5 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"پیش‌فرض شناسه تماس‌گیرنده روی محدود است. تماس بعدی: بدون محدودیت"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"پیش‌فرض شناسه تماس‌گیرنده روی غیرمحدود است. تماس بعدی: محدود"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"پیش‌فرض شناسه تماس‌گیرنده روی غیرمحدود است. تماس بعدی: بدون محدودیت"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"‏این برنامه با صفحه ۱۶ کیلوبایتی سازگار نیست. بررسی تراز APK ناموفق بود. این برنامه بااستفاده از حالت سازگار اندازه صفحه اجرا خواهد شد. برای بهترین سازگاری، لطفاً برنامه را با پشتیبانی از صفحه ۱۶ کیلوبایتی دوباره ترجمه کنید. برای اطلاعات بیشتر، به &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; مراجعه کنید"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"‏این برنامه با صفحه ۱۶ کیلوبایتی سازگار نیست. بررسی تراز ELF ناموفق بود. این برنامه بااستفاده از حالت سازگار اندازه صفحه اجرا خواهد شد. برای بهترین سازگاری، لطفاً برنامه را با پشتیبانی از صفحه ۱۶ کیلوبایتی دوباره ترجمه کنید. برای اطلاعات بیشتر، به &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; مراجعه کنید"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"‏این برنامه با صفحه ۱۶ کیلوبایتی سازگار نیست. بررسی‌های تراز APK و ELF ناموفق بود. این برنامه بااستفاده از حالت سازگار اندازه صفحه اجرا خواهد شد. برای بهترین سازگاری، لطفاً برنامه را با پشتیبانی از صفحه ۱۶ کیلوبایتی دوباره ترجمه کنید. برای اطلاعات بیشتر، به &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; مراجعه کنید"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"سرویس دارای مجوز نیست."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"‏شما می‎توانید تنظیم شناسه تماس‌گیرنده را تغییر دهید."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"داده به <xliff:g id="CARRIERDISPLAY">%s</xliff:g> تغییر کرد"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"تا <xliff:g id="COUNT">%d</xliff:g> ساعت دیگر"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"تا <xliff:g id="COUNT">%d</xliff:g> روز دیگر"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"تا <xliff:g id="COUNT">%d</xliff:g> سال دیگر"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# دقیقه قبل}one{# دقیقه قبل}other{# دقیقه قبل}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ساعت قبل}one{# ساعت قبل}other{# ساعت قبل}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# روز قبل}one{# روز قبل}other{# روز قبل}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"‫<xliff:g id="START">%1$s</xliff:g> تا <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"هر تقویمی"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> درحال قطع کردن بعضی از صداهاست"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی داده‌های کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 78fb2a07f756..2f57750c47ec 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Soittajan tunnukseksi muutetaan rajoitettu. Seuraava puhelu: ei rajoitettu"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: rajoitettu"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: ei rajoitettu"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Tämä sovellus ei tue 16 kt:tä. APK-sovelluksen yhteensopivuustarkistus epäonnistui. Sovellus käynnistetään sivukoon kanssa yhteensopivan tilan avulla. Parhaan yhteensopivuuden varmistamiseksi kokoa sovellus uudelleen niin, että se tukee 16 kt:tä. Katso lisätietoa osoitteesta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Tämä sovellus ei tue 16 kt:tä. ELF:n yhteensopivuustarkastus epäonnistui. Sovellus käynnistetään sivukoon kanssa yhteensopivan tilan avulla. Parhaan yhteensopivuuden varmistamiseksi kokoa sovellus uudelleen niin, että se tukee 16 kt:tä. Katso lisätietoa osoitteesta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Tämä sovellus ei tue 16 kt:tä. APK:n ja ELF:n yhteensopivuustarkastukset epäonnistuivat. Sovellus käynnistetään sivukoon kanssa yhteensopivan tilan avulla. Parhaan yhteensopivuuden varmistamiseksi kokoa sovellus uudelleen niin, että se tukee 16 kt:tä. Katso lisätietoa osoitteesta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Palvelua ei tarjota."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Et voi muuttaa soittajan tunnuksen asetusta."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Data vaihdettu: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> h:n päästä"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> pv:n päästä"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> v:n päästä"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuutti sitten}other{# minuuttia sitten}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# tunti sitten}other{# tuntia sitten}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# päivä sitten}other{# päivää sitten}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kaikki kalenterit"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mykistää joitakin ääniä"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Laitteellasi on sisäinen ongelma, joka aiheuttaa epävakautta. Voit korjata tilanteen palauttamalla tehdasasetukset."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 163d70d49230..a87fd677d37e 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : restreint"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Cette appli n\'est pas compatible avec les pages de 16 ko. La vérification de l\'alignement de fichiers APK a échoué. Cette appli sera exécutée en mode compatible avec la taille de la page. Pour une meilleure compatibilité, veuillez recompiler l\'application avec la prise en charge de pages de 16 ko. Pour en savoir plus, consultez la page &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Cette appli n\'est pas compatible avec les pages de 16 ko. La vérification de l\'alignement de fichiers ELF a échoué. Cette appli sera exécutée en mode compatible avec la taille de la page. Pour une meilleure compatibilité, veuillez recompiler l\'application avec la prise en charge de pages de 16 ko. Pour en savoir plus, consultez la page &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Cette appli n\'est pas compatible avec les pages de 16 ko. Les vérifications d\'alignement de fichiers APK et ELF ont échoué. Cette appli sera exécutée en mode compatible avec la taille de la page. Pour une meilleure compatibilité, veuillez recompiler l\'application avec la prise en charge de pages de 16 ko. Pour en savoir plus, consultez la page &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Données changées à <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"dans <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"dans <xliff:g id="COUNT">%d</xliff:g> j"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"dans <xliff:g id="COUNT">%d</xliff:g> a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Il y a # minute}one{Il y a # minute}many{Il y a # minutes}other{Il y a # minutes}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Il y a # heure}one{Il y a # heure}many{Il y a # heures}other{Il y a # heures}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Il y a # jour}one{Il y a # jour}many{Il y a # jours}other{Il y a # jours}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> à <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"N\'importe quel agenda"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> désactive certains sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne est survenu avec votre appareil. Il se peut qu\'il soit instable jusqu\'à ce que vous le réinitialisiez à ses paramètres par défaut."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 75b8e3125da0..ba09a532be06 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : restreint"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Cette appli n\'est pas compatible avec les pages de 16 ko. Échec de la vérification de l\'alignement de l\'APK. Cette appli sera exécutée dans un mode compatible avec la taille de la page. Pour une compatibilité optimale, veuillez recompiler l\'application de manière à ce que la taille de 16 ko soit prise en charge. Pour en savoir plus, consultez &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Cette appli n\'est pas compatible avec les pages de 16 ko. Échec de la vérification de l\'alignement de l\'ELF. Cette appli sera exécutée dans un mode compatible avec la taille de la page. Pour une compatibilité optimale, veuillez recompiler l\'application de manière à ce que la taille de 16 ko soit prise en charge. Pour en savoir plus, consultez &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Cette appli n\'est pas compatible avec les pages de 16 ko. Échec des vérifications de l\'alignement de l\'APK et de l\'ELF. Cette appli sera exécutée dans un mode compatible avec la taille de la page. Pour une compatibilité optimale, veuillez recompiler l\'application de manière à ce que la taille de 16 ko soit prise en charge. Pour en savoir plus, consultez &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Données transférées vers <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"dans <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"dans <xliff:g id="COUNT">%d</xliff:g> j"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"dans <xliff:g id="COUNT">%d</xliff:g> an"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Il y a # minute}one{Il y a # minute}many{Il y a # minutes}other{Il y a # minutes}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Il y a # heure}one{Il y a # heure}many{Il y a # heures}other{Il y a # heures}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Il y a # jour}one{Il y a # jour}many{Il y a # jours}other{Il y a # jours}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> à <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Tous les agendas"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> coupe certains sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne lié à votre appareil est survenu. Ce dernier risque d\'être instable jusqu\'à ce que vous rétablissiez la configuration d\'usine."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 70a7ec9c7a19..c46436f7e311 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O valor predeterminado do identificador de chamada é non restrinxido. Próxima chamada: restrinxido"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Esta aplicación non é compatible con 16 kB. Produciuse un erro ao verificar o aliñamento de ficheiros APK. Esta aplicación executarase usando o modo compatible co tamaño de páxina. Para obter a mellor compatibilidade, volve compilar a aplicación con asistencia de 16 kB. Se precisas máis información, atoparala en &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Esta aplicación non é compatible con 16 kB. Produciuse un erro ao verificar o aliñamento de ficheiros ELF. Esta aplicación executarase usando o modo compatible co tamaño de páxina. Para obter a mellor compatibilidade, volve compilar a aplicación con asistencia de 16 kB. Se precisas máis información, atoparala en &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Esta aplicación non é compatible con 16 kB. Produciuse un erro ao verificar o aliñamento de ficheiros APK e ELF. Esta aplicación executarase usando o modo compatible co tamaño de páxina. Para obter a mellor compatibilidade, volve compilar a aplicación con asistencia de 16 kB. Se precisas máis información, atoparala en &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizo non ofrecido."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Non podes cambiar a configuración do identificador de chamada."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Cambiouse a conexión de datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"en <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"en <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"en <xliff:g id="COUNT">%d</xliff:g> a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Hai # minuto}other{Hai # minutos}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Hai # hora}other{Hai # horas}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Hai # día}other{Hai # días}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Calquera calendario"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando algúns sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Produciuse un erro interno no teu dispositivo e quizais funcione de maneira inestable ata o restablecemento dos datos de fábrica."</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 1535e4f5d463..89f5a59ca916 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"કૉલર ID પ્રતિબંધિત પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"આ ઍપ 16 KB સુસંગત નથી. APK સંરેખણની તપાસ નિષ્ફળ રહી. આ ઍપ પેજના કદ સાથે સુસંગત મોડનો ઉપયોગ કરીને ચાલશે. શ્રેષ્ઠ સુસંગતતા માટે, કૃપા કરીને 16 KB સપોર્ટવાળી ઍપ્લિકેશન ફરીથી સંકલિત કરો. વધુ માહિતી માટે, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; જુઓ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"આ ઍપ 16 KB સુસંગત નથી. ELF સંરેખણની તપાસ નિષ્ફળ રહી. આ ઍપ પેજના કદ સાથે સુસંગત મોડનો ઉપયોગ કરીને ચાલશે. શ્રેષ્ઠ સુસંગતતા માટે, કૃપા કરીને 16 KB સપોર્ટવાળી ઍપ્લિકેશન ફરીથી સંકલિત કરો. વધુ માહિતી માટે, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; જુઓ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"આ ઍપ 16 KB સુસંગત નથી. APK અને ELF સંરેખણની તપાસ નિષ્ફળ રહી. આ ઍપ પેજના કદ સાથે સુસંગત મોડનો ઉપયોગ કરીને ચાલશે. શ્રેષ્ઠ સુસંગતતા માટે, કૃપા કરીને 16 KB સપોર્ટવાળી ઍપ્લિકેશન ફરીથી સંકલિત કરો. વધુ માહિતી માટે, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; જુઓ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ડેટા <xliff:g id="CARRIERDISPLAY">%s</xliff:g> પર સ્વિચ કર્યો"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> કલાકમાં"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> દિવસમાં"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> વર્ષમાં"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# મિનિટ પહેલાં}one{# મિનિટ પહેલાં}other{# મિનિટ પહેલાં}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# કલાક પહેલાં}one{# કલાક પહેલાં}other{# કલાક પહેલાં}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# દિવસ પહેલાં}one{# દિવસ પહેલાં}other{# દિવસ પહેલાં}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>થી <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"કોઈપણ કૅલેન્ડર"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> અમુક અવાજોને મ્યૂટ કરે છે"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે અને જ્યાં સુધી તમે ફેક્ટરી ડેટા ફરીથી સેટ કરશો નહીં ત્યાં સુધી તે અસ્થિર રહી શકે છે."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index c5825fe097db..4bdfa6baf14d 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित है. अगली कॉल: सीमित नहीं"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित नहीं"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"यह ऐप्लिकेशन 16 केबी वाले पेजों के साथ काम नहीं करता. APK के अलाइनमेंट की जांच नहीं की जा सकी. यह ऐप्लिकेशन, पेज साइज़ के साथ काम करने वाले मोड का इस्तेमाल करके चलेगा. पेज साइज़ के साथ बेहतर तरीके से काम के लिए, कृपया 16 केबी वाले पेजों के साथ ऐप्लिकेशन को फिर से कंपाइल करें. ज़्यादा जानकारी के लिए, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पर जाएं"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"यह ऐप्लिकेशन 16 केबी वाले पेजों के साथ काम नहीं करता. ईएलएफ़ अलाइनमेंट की जांच नहीं की जा सकी. यह ऐप्लिकेशन, पेज साइज़ के साथ काम करने वाले मोड का इस्तेमाल करके चलेगा. पेज साइज़ के साथ बेहतर तरीके से काम के लिए, कृपया 16 केबी वाले पेजों के साथ ऐप्लिकेशन को फिर से कंपाइल करें. ज़्यादा जानकारी के लिए, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पर जाएं"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"यह ऐप्लिकेशन 16 केबी वाले पेजों के साथ काम नहीं करता. APK और ईएलएफ़ के अलाइनमेंट की जांच नहीं की जा सकी. यह ऐप्लिकेशन, पेज साइज़ के साथ काम करने वाले मोड का इस्तेमाल करके चलेगा. पेज साइज़ के साथ बेहतर तरीके से काम के लिए, कृपया 16 केबी वाले पेजों के साथ ऐप्लिकेशन को फिर से कंपाइल करें. ज़्यादा जानकारी के लिए, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पर जाएं"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवा प्रावधान की हुई नहीं है."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"आप कॉलर आईडी सेटिंग नहीं बदल सकते."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा को <xliff:g id="CARRIERDISPLAY">%s</xliff:g> पर स्विच किया गया"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> घंटे में"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> दिन में"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> साल में"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# मिनट पहले}one{# मिनट पहले}other{# मिनट पहले}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# घंटा पहले}one{# घंटा पहले}other{# घंटे पहले}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# दिन पहले}one{# दिन पहले}other{# दिन पहले}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> से <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"किसी भी कैलेंडर के इवेंट के लिए"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> कुछ आवाज़ें म्‍यूट कर रहा है"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"आपके डिवाइस में कोई अंदरूनी समस्या है और यह तब तक ठीक नहीं होगी जब तक आप फ़ैक्‍टरी डेटा रीसेट नहीं करते."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 954f4cb1842c..cd7d02343217 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zadana postavka ID-a pozivatelja ima ograničenje. Sljedeći poziv: Nije ograničen"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Ograničen"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Nije ograničen"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 KB. Provjera poravnanja APK-a nije uspjela. Ova će se aplikacija pokrenuti pomoću načina kompatibilnog s veličinom stranice. Za najbolju kompatibilnost ponovo kompilirajte aplikaciju s podrškom od 16 KB. Više informacija potražite na stranici &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 KB. Provjera poravnanja ELF-a nije uspjela. Ova će se aplikacija pokrenuti pomoću načina kompatibilnog s veličinom stranice. Za najbolju kompatibilnost ponovo kompilirajte aplikaciju s podrškom od 16 KB. Više informacija potražite na stranici &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ova aplikacija nije kompatibilna s veličinom stranice od 16 KB. Provjere poravnanja APK-a i ELF-a nisu uspjele. Ova će se aplikacija pokrenuti pomoću načina kompatibilnog s veličinom stranice. Za najbolju kompatibilnost ponovo kompilirajte aplikaciju s podrškom od 16 KB. Više informacija potražite na stranici &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije rezervirana."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavku ID-a pozivatelja."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> g."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Prije # min}one{Prije # min}few{Prije # min}other{Prije # min}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Prije # h}one{Prije # h}few{Prije # h}other{Prije # h}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Prije # dan}one{Prije # dan}few{Prije # dana}other{Prije # dana}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Na vašem uređaju postoji interni problem i možda neće biti stabilan dok ga ne vratite na tvorničko stanje."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 58a5cbdbc2ab..ddc8762931b9 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"A hívóazonosító alapértelmezett értéke korlátozott. Következő hívás: nem korlátozott"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: korlátozott"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: nem korlátozott"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ez az alkalmazás nem kompatibilis a 16 kB-os mérettel. Az APK-igazítási ellenőrzés sikertelen volt. Ez az app az oldalméret-kompatibilis mód használatával fog futni. A legjobb kompatibilitás érdekében fordítsa újra az alkalmazást 16 kB-os támogatással. További információ: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ez az alkalmazás nem kompatibilis a 16 kB-os mérettel. Az ELF-igazítási ellenőrzés sikertelen. Ez az app az oldalméret-kompatibilis mód használatával fog futni. A legjobb kompatibilitás érdekében fordítsa újra az alkalmazást 16 kB-os támogatással. További információ: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ez az alkalmazás nem kompatibilis a 16 kB-os mérettel. Az APK- és ELF-igazítási ellenőrzések sikertelenek voltak. Ez az app az oldalméret-kompatibilis mód használatával fog futni. A legjobb kompatibilitás érdekében fordítsa újra az alkalmazást 16 kB-os támogatással. További információ: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"A szolgáltatás nincs biztosítva."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nem tudja módosítani a hívó fél azonosítója beállítást."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Adatforgalom átváltva a következőre: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> ó múlva"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> n múlva"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> é múlva"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# perce}other{# perce}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# órája}other{# órája}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# napja}other{# napja}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bármilyen naptár"</string>
<string name="muted_by" msgid="91464083490094950">"A(z) <xliff:g id="THIRD_PARTY">%1$s</xliff:g> lenémít néhány hangot"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Belső probléma van az eszközzel, és instabil lehet, amíg vissza nem állítja a gyári adatokat."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index f457c42d9cd4..3e961e74d17b 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Զանգողի ID-ն լռելյայն սահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` Սահմանափակված"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Այս հավելվածը հարմարեցված չէ 16 ԿԲ չափի համար։ APK-ի հետ համատեղելիությունը ձախողվել է։ Այս հավելվածը կաշխատի էջի չափի հետ համատեղելի ռեժիմում։ Համատեղելիությունն ապահովելու համար նորից կոմպիլացրեք հավելվածը 16 ԿԲ չափն աջակցելու համար։ Լրացուցիչ տեղեկություններ ստանալու համար այցելեք &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Այս հավելվածը հարմարեցված չէ 16 ԿԲ չափի համար։ ELF-ի հետ համատեղելիությունը ձախողվել է։ Այս հավելվածը կաշխատի էջի չափի հետ համատեղելի ռեժիմում։ Համատեղելիությունն ապահովելու համար նորից կոմպիլացրեք հավելվածը 16 ԿԲ չափն աջակցելու համար։ Լրացուցիչ տեղեկություններ ստանալու համար այցելեք &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Այս հավելվածը հարմարեցված չէ 16 ԿԲ չափի համար։ APK-ի և ELF-ի հետ համատեղելիության ստուգումը ձախողվել է։ Այս հավելվածը կաշխատի էջի չափի հետ համատեղելի ռեժիմում։ Համատեղելիությունն ապահովելու համար նորից կոմպիլացրեք հավելվածը 16 ԿԲ չափն աջակցելու համար։ Լրացուցիչ տեղեկություններ ստանալու համար այցելեք &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Օգտագործվում է <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ի բջջային ինտերնետը"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> ժամից"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> օրից"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> տարուց"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# րոպե առաջ}one{# րոպե առաջ}other{# րոպե առաջ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ժամ առաջ}one{# ժամ առաջ}other{# ժամ առաջ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# օր առաջ}one{# օր առաջ}other{# օր առաջ}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Ցանկացած օրացույց"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ն անջատում է որոշ ձայներ"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Սարքում ներքին խնդիր է առաջացել և այն կարող է կրկնվել, մինչև չվերականգնեք գործարանային կարգավորումները:"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 88764c4112b8..594c173b907c 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID penelepon diatur default ke \"dibatasi\". Panggilan selanjutnya: Tidak dibatasi."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Dibatasi"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Aplikasi ini tidak kompatibel dengan 16 KB. Pemeriksaan keselarasan APK gagal. Aplikasi ini akan dijalankan menggunakan mode yang kompatibel dengan ukuran halaman. Untuk kompatibilitas terbaik, kompilasi ulang aplikasi dengan dukungan 16 KB. Untuk mengetahui informasi selengkapnya, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Aplikasi ini tidak kompatibel dengan 16 KB. Pemeriksaan keselarasan ELF gagal. Aplikasi ini akan dijalankan menggunakan mode yang kompatibel dengan ukuran halaman. Untuk kompatibilitas terbaik, kompilasi ulang aplikasi dengan dukungan 16 KB. Untuk mengetahui informasi selengkapnya, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Aplikasi ini tidak kompatibel dengan 16 KB. Pemeriksaan keselarasan APK dan ELF gagal. Aplikasi ini akan dijalankan menggunakan mode yang kompatibel dengan ukuran halaman. Untuk kompatibilitas terbaik, kompilasi ulang aplikasi dengan dukungan 16 KB. Untuk mengetahui informasi selengkapnya, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Layanan tidak diperlengkapi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak dapat mengubah setelan ID penelepon."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Mengalihkan data seluler ke <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"dalam <xliff:g id="COUNT">%d</xliff:g> j"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"dalam <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"dalam <xliff:g id="COUNT">%d</xliff:g> t"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# menit lalu}other{# menit lalu}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# jam lalu}other{# jam lalu}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# hari lalu}other{# hari lalu}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hingga <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kalender mana saja"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mematikan beberapa suara"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Ada masalah dengan perangkat. Hal ini mungkin membuat perangkat jadi tidak stabil dan perlu dikembalikan ke setelan pabrik."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 8e92ed1532c1..0242fa3e43d6 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Númerabirting er sjálfgefið með takmörkunum. Næsta símtal: Án takmarkana"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Með takmörkunum"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Án takmarkana"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Þetta forrit er ekki samhæft 16 KB. Athugun á samræmi við APK mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Þetta forrit er ekki samhæft 16 KB. Athugun á samræmi við ELF mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Þetta forrit er ekki samhæft 16 KB. Athuganir á samræmingu APK og ELF mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Þjónustu ekki útdeilt."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Þú getur ekki breytt stillingu númerabirtingar."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Skipt yfir í farsímagögn hjá <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"eftir <xliff:g id="COUNT">%d</xliff:g> klst."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"eftir <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"eftir <xliff:g id="COUNT">%d</xliff:g> ár"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Fyrir # mínútu}one{Fyrir # mínútu}other{Fyrir # mínútum}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Fyrir # klukkustund}one{Fyrir # klukkustund}other{Fyrir # klukkustundum}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Fyrir # degi}one{Fyrir # degi}other{Fyrir # dögum}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Öll dagatöl"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> þaggar í einhverjum hljóðum"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Innra vandamál kom upp í tækinu og það kann að vera óstöðugt þangað til þú núllstillir það."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 49f1f21131e2..e912417fe4d7 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID chiamante generalmente limitato. Prossima chiamata: non limitato"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID chiamante generalmente non limitato. Prossima chiamata: limitato"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Questa app non è compatibile con 16 kB. Controllo allineamento APK non riuscito. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Questa app non è compatibile con 16 kB. Controllo allineamento ELF non riuscito. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Questa app non è compatibile con 16 kB. Controlli di allineamento APK ed ELF non riusciti. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizio non fornito."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Non è possibile modificare l\'impostazione ID chiamante."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"I dati sono stati trasferiti a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -169,7 +172,7 @@
<string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ok"</string>
<string name="fcComplete" msgid="1080909484660507044">"Codice funzione completo."</string>
<string name="fcError" msgid="5325116502080221346">"Problema di connessione o codice funzione non valido."</string>
- <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string>
+ <string name="httpErrorOk" msgid="6206751415788256357">"Ok"</string>
<string name="httpError" msgid="3406003584150566720">"Si è verificato un errore di rete."</string>
<string name="httpErrorLookup" msgid="3099834738227549349">"Impossibile trovare l\'URL."</string>
<string name="httpErrorUnsupportedAuthScheme" msgid="3976195595501606787">"Schema di autenticazione del sito non supportato."</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"tra <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"tra <xliff:g id="COUNT">%d</xliff:g> g"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"tra <xliff:g id="COUNT">%d</xliff:g> a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuto fa}many{# di minuti fa}other{# minuti fa}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ora fa}many{# di ore fa}other{# ore fa}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# giorno fa}many{# di giorni fa}other{# giorni fa}}"</string>
@@ -1167,7 +1202,7 @@
<string name="VideoView_error_title" msgid="5750686717225068016">"Problemi video"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Questo video non è valido per lo streaming su questo dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Impossibile riprodurre il video."</string>
- <string name="VideoView_error_button" msgid="5138809446603764272">"OK"</string>
+ <string name="VideoView_error_button" msgid="5138809446603764272">"Ok"</string>
<string name="relative_time" msgid="8572030016028033243">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="noon" msgid="8365974533050605886">"mezzogiorno"</string>
<string name="Noon" msgid="6902418443846838189">"Mezzogiorno"</string>
@@ -1206,7 +1241,7 @@
<string name="app_running_notification_text" msgid="5120815883400228566">"Tocca per ulteriori informazioni o per interrompere l\'app."</string>
<string name="ok" msgid="2646370155170753815">"Ok"</string>
<string name="cancel" msgid="6908697720451760115">"Annulla"</string>
- <string name="yes" msgid="9069828999585032361">"OK"</string>
+ <string name="yes" msgid="9069828999585032361">"Ok"</string>
<string name="no" msgid="5122037903299899715">"Annulla"</string>
<string name="dialog_alert_title" msgid="651856561974090712">"Attenzione"</string>
<string name="loading" msgid="3138021523725055037">"Caricamento…"</string>
@@ -1265,7 +1300,7 @@
<string name="anr_activity_process" msgid="3477362583767128667">"L\'app <xliff:g id="ACTIVITY">%1$s</xliff:g> non risponde"</string>
<string name="anr_application_process" msgid="4978772139461676184">"L\'app <xliff:g id="APPLICATION">%1$s</xliff:g> non risponde"</string>
<string name="anr_process" msgid="1664277165911816067">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> non risponde"</string>
- <string name="force_close" msgid="9035203496368973803">"OK"</string>
+ <string name="force_close" msgid="9035203496368973803">"Ok"</string>
<string name="report" msgid="2149194372340349521">"Segnala"</string>
<string name="wait" msgid="7765985809494033348">"Attendi"</string>
<string name="webpage_unresponsive" msgid="7850879412195273433">"La pagina non risponde più.\n\nVuoi chiuderla?"</string>
@@ -1395,7 +1430,7 @@
<string name="perms_description_app" msgid="2747752389870161996">"Fornito da <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
<string name="no_permissions" msgid="5729199278862516390">"Nessuna autorizzazione richiesta"</string>
<string name="perm_costs_money" msgid="749054595022779685">"potrebbe comportare dei costi"</string>
- <string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
+ <string name="dlg_ok" msgid="5103447663504839312">"Ok"</string>
<string name="usb_charging_notification_title" msgid="1674124518282666955">"Dispositivo in carica tramite USB"</string>
<string name="usb_supplying_notification_title" msgid="5378546632408101811">"Dispositivo collegato in carica tramite USB"</string>
<string name="usb_mtp_notification_title" msgid="1065989144124499810">"Trasferimento file tramite USB attivato"</string>
@@ -1892,7 +1927,7 @@
<string name="restr_pin_try_later" msgid="5897719962541636727">"Riprova più tardi"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualizzazione a schermo intero"</string>
<string name="immersive_cling_description" msgid="2896205051090870978">"Per uscire, scorri verso il basso dalla parte superiore dello schermo"</string>
- <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+ <string name="immersive_cling_positive" msgid="7047498036346489883">"Ok"</string>
<string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ruota per migliorare l\'anteprima"</string>
<string name="display_rotation_camera_compat_toast_in_multi_window" msgid="2473122980393502775">"Apri <xliff:g id="NAME">%s</xliff:g> a schermo intero per migliorare la visualizzazione"</string>
<string name="done_label" msgid="7283767013231718521">"Fine"</string>
@@ -1914,7 +1949,7 @@
<string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"Aggiornato dall\'amministratore"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminato dall\'amministratore"</string>
- <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</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>
<string name="battery_saver_description" msgid="8518809702138617167">"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>
<string name="data_saver_description" msgid="4995164271550590517">"Per contribuire a ridurre l\'utilizzo dei dati, la funzionalità Risparmio dati impedisce ad alcune app di inviare o ricevere dati in background. Un\'app in uso può accedere ai dati, ma potrebbe farlo con meno frequenza. Per esempio, è possibile che le immagini non vengano visualizzate finché non le tocchi."</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Da <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsiasi calendario"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string>
@@ -2150,7 +2186,7 @@
<string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Questa notifica è stata posizionata più in basso. Tocca per dare un feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notifiche avanzate"</string>
<string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Ora le risposte e le azioni suggerite vengono fornite dalle notifiche avanzate. Le notifiche adattive Android non sono più supportate."</string>
- <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
+ <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"Ok"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Disattiva"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Scopri di più"</string>
<string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Le notifiche adattive Android sono state sostituite dalle notifiche avanzate in Android 12. Questa funzionalità mostra risposte e azioni suggerite e organizza le tue notifiche.\n\nLe notifiche avanzate possono accedere ai contenuti di una notifica, incluse le informazioni personali, come i nomi dei contatti e i messaggi. Questa funzionalità può anche ignorare le notifiche o rispondervi, ad esempio accettando le telefonate, e controllare la modalità Non disturbare."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0e8f472cb107..a9ce2d284528 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"שירות השיחה המזוהה עובר כברירת מחדל למצב מוגבל. השיחה הבאה: לא מוגבלת"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"שירות \'שיחה מזוהה\' עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: מוגבלת"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"זיהוי מתקשר עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: לא מוגבלת"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"‏האפליקציה הזו לא תואמת לדפים בגודל 16KB. בדיקות ההתאמה ל-APK נכשלה. האפליקציה הזו תופעל במצב תואם לגודל הדף. כדי לקבל את התאימות הטובה ביותר, צריך להדר מחדש (recompile) את האפליקציה לתמיכה בדפים בגודל 16KB. מידע נוסף זמין בכתובת &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"‏האפליקציה הזו לא תואמת לדפים בגודל 16KB. בדיקות ההתאמה ל-ELF נכשלה. האפליקציה הזו תופעל במצב תואם לגודל הדף. כדי לקבל את התאימות הטובה ביותר, צריך להדר מחדש (recompile) את האפליקציה לתמיכה בדפים בגודל 16KB. מידע נוסף זמין בכתובת &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"‏האפליקציה הזו לא תואמת לדפים בגודל 16KB. בדיקות ההתאמה ל-APK ול-ELF נכשלו. האפליקציה הזו תופעל במצב תואם לגודל הדף. כדי לקבל את התאימות הטובה ביותר, צריך להדר מחדש (recompile) את האפליקציה לתמיכה בדפים בגודל 16KB. מידע נוסף זמין בכתובת &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"השירות לא הוקצה."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"אינך יכול לשנות את הגדרת זיהוי המתקשר."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"הנתונים עברו אל <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"בעוד <xliff:g id="COUNT">%d</xliff:g> שע‘"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"בעוד <xliff:g id="COUNT">%d</xliff:g> י‘"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"בעוד <xliff:g id="COUNT">%d</xliff:g> שנים"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{לפני דקה}one{לפני # דקות}two{לפני # דקות}other{לפני # דקות}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{לפני שעה}one{לפני # שעות}two{לפני שעתיים}other{לפני # שעות}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{לפני יום}one{לפני # ימים}two{לפני יומיים}other{לפני # ימים}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"‫<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"‫<xliff:g id="START">%1$s</xliff:g> עד <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"כל יומן"</string>
<string name="muted_by" msgid="91464083490094950">"חלק מהצלילים מושתקים על ידי <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"קיימת בעיה פנימית במכשיר שלך, וייתכן שהוא לא יתפקד כראוי עד שיבוצע איפוס לנתוני היצרן."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 6eabd4afaf3d..07fa8bffa08f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"既定: 発信者番号非通知、次の発信: 通知"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"既定: 発信者番号通知、次の発信: 非通知"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"既定: 発信者番号通知、次の発信: 通知"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"このアプリは 16 KB アライメントではありません。APK のアライメント チェックが失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"このアプリは 16 KB アライメントではありません。ELF のアライメント チェックに失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"このアプリは 16 KB アライメントではありません。APK と ELF のアライメント チェックが失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"提供可能なサービスがありません。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"発信者番号の設定は変更できません。"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"データが <xliff:g id="CARRIERDISPLAY">%s</xliff:g> に切り替わりました"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> 時間後"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> 日後"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> 年後"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# 分前}other{# 分前}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# 時間前}other{# 時間前}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# 日前}other{# 日前}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>、<xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"すべてのカレンダー"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> により一部の音はミュートに設定"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"デバイスで内部的な問題が発生しました。データが初期化されるまで不安定になる可能性があります。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 4ffa312f7c53..b52896b69646 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ნაგულისხმებად დაყენებულია ნომრის დაფარვა. შემდეგი ზარი: არ არის დაფარული."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: დაფარულია."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. APK გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. ELF გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. APK და ELF გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"მონაცემები გადართულია <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ზე"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> საათში"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> დღეში"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> წელში"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# წუთის წინ}other{# წუთის წინ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# საათის წინ}other{# საათის წინ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# დღის წინ}other{# დღის წინ}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ნებისმიერი კალენდარი"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ზოგიერთ ხმას ადუმებს"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ფიქსირდება თქვენი მ ოწყობილობის შიდა პრობლემა და შეიძლება არასტაბილური იყოს, სანამ ქარხნულ მონაცემების არ განაახლებთ."</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 6355d86a8ac9..384969029f3c 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелген. Келесі қоңырау: Шектелмеген"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелген"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Бұл қолданба 16 КБ көлеміне қолдау көрсетпейді. APK туралау тексерісі орындалмады. Бұл қолданба бет өлшемімен сәйкестік режимінде іске қосылады. Сәйкестікті жақсарту үшін қолданбаны 16 КБ көлемін қолдайтындай етіп қайта құрастырыңыз. Қосымша ақпарат алу үшін &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; сілтемесін қарап шығыңыз."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Бұл қолданба 16 КБ көлеміне қолдау көрсетпейді. ELF туралау тексерісі орындалмады. Бұл қолданба бет өлшемімен сәйкестік режимінде іске қосылады. Сәйкестікті жақсарту үшін қолданбаны 16 КБ көлемін қолдайтындай етіп қайта құрастырыңыз. Қосымша ақпарат алу үшін &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; сілтемесін қарап шығыңыз."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Бұл қолданба 16 КБ көлеміне қолдау көрсетпейді. APK және ELF файлдарының туралау тексерісі орындалмады. Бұл қолданба бет өлшемімен сәйкестік режимінде іске қосылады. Сәйкестікті жақсарту үшін қолданбаны 16 КБ көлемін қолдайтындай етіп қайта құрастырыңыз. Қосымша ақпарат алу үшін &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; сілтемесін қарап шығыңыз."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Деректер <xliff:g id="CARRIERDISPLAY">%s</xliff:g> операторына ауыстырылды"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> сағ кейін"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> күннен кейін"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> жылдан кейін"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# минут бұрын}other{# минут бұрын}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# сағат бұрын}other{# сағат бұрын}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# күн бұрын}other{# күн бұрын}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Кез келген күнтізбе"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> кейбір дыбыстарды өшіруде"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
@@ -2442,9 +2478,9 @@
<string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"Satellite SOS функциясы қолжетімді емес"</string>
<string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"Satellite SOS функциясы бұл елде немесе аймақта қолжетімді емес."</string>
<string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"Satellite SOS функциясы реттелмеген"</string>
- <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"Жерсерік арқылы хабар алмасу үшін, Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
+ <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"Жерсерік арқылы хабар алмасу үшін Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
<string name="satellite_sos_location_disabled_notification_title" msgid="5427987916850950591">"Satellite SOS функциясы қолжетімді емес"</string>
- <string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"Satellite SOS функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін, локация параметрлерін қосыңыз."</string>
+ <string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"Satellite SOS функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін локация параметрлерін қосыңыз."</string>
<string name="satellite_messaging_available_notification_title" msgid="3366657987618784706">"Жерсерік арқылы хабар алмасу функциясы қолжетімді"</string>
<string name="satellite_messaging_available_notification_summary" msgid="7573949038500243670">"Мобильдік немесе Wi-Fi желісі жоқ болған жағдайда, жерсерік арқылы хабар алмаса аласыз. Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
<string name="satellite_messaging_not_supported_notification_title" msgid="8202139632766878610">"Жерсерік арқылы хабар алмасу функциясына қолдау көрсетілмейді"</string>
@@ -2454,9 +2490,9 @@
<string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"Жерсерік арқылы хабар алмасу функциясы қолжетімді емес"</string>
<string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"Жерсерік арқылы хабар алмасу функциясы бұл елде немесе аймақта қолжетімді емес."</string>
<string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"Жерсерік арқылы хабар алмасу функциясы реттелмеген"</string>
- <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"Жерсерік арқылы хабар алмасу үшін, Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"Жерсерік арқылы хабар алмасу үшін Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
<string name="satellite_messaging_location_disabled_notification_title" msgid="7270641894250928494">"Жерсерік арқылы хабар алмасу функциясы қолжетімді емес"</string>
- <string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"Жерсерік арқылы хабар алмасу функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін, локация параметрлерін қосыңыз."</string>
+ <string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"Жерсерік арқылы хабар алмасу функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін локация параметрлерін қосыңыз."</string>
<string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Саусақ ізімен ашу функциясын қайта реттеу"</string>
<string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> бұдан былай танылмайды."</string>
<string name="fingerprint_dangling_notification_msg_2" msgid="7925203589860744456">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> және <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> бұдан былай танылмайды."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 4163be41f7fd..27299c5ee9f8 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"មិន​បាន​ដាក់កម្រិត​លំនាំដើម​លេខ​សម្គាល់​អ្នក​ហៅ។ ការ​ហៅ​បន្ទាប់៖ មិន​បាន​ដាក់​កម្រិត។"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"មិន​បាន​ដាក់​កម្រិត​លេខ​សម្គាល់​អ្នក​ហៅ​លំនាំ​ដើម។ ការ​ហៅ​បន្ទាប់៖​ បាន​ដាក់កម្រិត"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"មិន​បាន​ដាក់កម្រិត​លំនាំដើម​លេខ​សម្គាល់​អ្នក​ហៅ។ ការ​ហៅ​បន្ទាប់៖ មិន​បាន​ដាក់​កម្រិត។"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"កម្មវិធីនេះមិនត្រូវគ្នានឹង 16 KB ទេ។ ការត្រួតពិនិត្យការតម្រឹម APK មិនបានសម្រេចទេ។ កម្មវិធីនេះនឹងត្រូវបានដំណើរការដោយប្រើមុខងារដែលត្រូវគ្នានឹងទំហំទំព័រ។ ដើម្បីទទួលបានភាពត្រូវគ្នាល្អបំផុត សូមចងក្រងកម្មវិធីឡើងវិញដោយប្រើជំនួយ 16 KB។ ដើម្បីទទួលបានព័ត៌មានបន្ថែម សូមមើល &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"កម្មវិធីនេះមិនត្រូវគ្នានឹង 16 KB ទេ។ ការត្រួតពិនិត្យការតម្រឹម ELF មិនបានសម្រេចទេ។ កម្មវិធីនេះនឹងត្រូវបានដំណើរការដោយប្រើមុខងារដែលត្រូវគ្នានឹងទំហំទំព័រ។ ដើម្បីទទួលបានភាពត្រូវគ្នាល្អបំផុត សូមចងក្រងកម្មវិធីឡើងវិញដោយប្រើជំនួយ 16 KB។ ដើម្បីទទួលបានព័ត៌មានបន្ថែម សូមមើល &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"កម្មវិធីនេះមិនត្រូវគ្នានឹង 16 KB ទេ។ ការត្រួតពិនិត្យការតម្រឹម APK និង ELF មិនបានសម្រេចទេ។ កម្មវិធីនេះនឹងត្រូវបានដំណើរការដោយប្រើមុខងារដែលត្រូវគ្នានឹងទំហំទំព័រ។ ដើម្បីទទួលបានភាពត្រូវគ្នាល្អបំផុត សូមចងក្រងកម្មវិធីឡើងវិញដោយប្រើជំនួយ 16 KB។ ដើម្បីទទួលបានព័ត៌មានបន្ថែម សូមមើល &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"មិន​បាន​ផ្ដល់​សេវាកម្ម។"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"អ្នក​មិន​អាច​ប្ដូរ​ការ​កំណត់​លេខ​សម្គាល់​អ្នក​ហៅ​បានទេ។"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"បានប្ដូរទិន្នន័យទៅ <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"ក្នុងរយៈពេល <xliff:g id="COUNT">%d</xliff:g>ម៉"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"ក្នុងរយៈពេល <xliff:g id="COUNT">%d</xliff:g>ថ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"ក្នុងរយៈពេល <xliff:g id="COUNT">%d</xliff:g>ឆ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# នាទី​មុន}other{# នាទីមុន}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ម៉ោងមុន}other{# ម៉ោងមុន}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ថ្ងៃមុន}other{# ថ្ងៃមុន}}"</string>
@@ -1218,7 +1253,7 @@
<string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{ផ្កាយមួយ​ក្នុងចំណោមផ្កាយ {max}}other{ផ្កាយ # ក្នុងចំណោមផ្កាយ {max}}}"</string>
<string name="in_progress" msgid="2149208189184319441">"កំពុងដំណើរការ"</string>
<string name="whichApplication" msgid="5432266899591255759">"បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ"</string>
- <string name="whichApplicationNamed" msgid="6969946041713975681">"បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ​ %%1$s"</string>
+ <string name="whichApplicationNamed" msgid="6969946041713975681">"បញ្ចប់​សកម្មភាព​ដោយ​ប្រើ​ %1$s"</string>
<string name="whichApplicationLabel" msgid="7852182961472531728">"បញ្ចប់សកម្មភាព"</string>
<string name="whichViewApplication" msgid="5733194231473132945">"បើក​ជា​មួយ"</string>
<string name="whichViewApplicationNamed" msgid="415164730629690105">"បើក​ជាមួយ %1$s"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ដល់ <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ប្រតិទិនណាមួយ"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> កំពុង​បិទសំឡេង​មួយចំនួន"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ហើយវាអ្នកមិនមានស្ថេរភាព រហូតទាល់តែអ្នកកំណត់ដូចដើមវិញទាំងស្រុង។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 234ba66a5bc3..85352bc18fdf 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಿಲ್ಲ"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. APK ಮತ್ತು ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿವೆ ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿದೆ. ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. APK ಮತ್ತು ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿವೆ ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್‌ ID ಸೆಟ್ಟಿಂಗ್‌ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> ಗೆ ಡೇಟಾವನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>ಗಂ ಯಲ್ಲಿ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>ದಿ ದಲ್ಲಿ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>ವ ದಲ್ಲಿ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# ನಿಮಿಷದ ಹಿಂದೆ}one{# ನಿಮಿಷಗಳ ಹಿಂದೆ}other{# ನಿಮಿಷಗಳ ಹಿಂದೆ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ಗಂಟೆಯ ಹಿಂದೆ}one{# ಗಂಟೆಗಳ ಹಿಂದೆ}other{# ಗಂಟೆಗಳ ಹಿಂದೆ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ದಿನದ ಹಿಂದೆ}one{# ದಿನಗಳ ಹಿಂದೆ}other{# ದಿನಗಳ ಹಿಂದೆ}}"</string>
@@ -1387,7 +1422,7 @@
<string name="carrier_app_notification_title" msgid="5815477368072060250">"ಹೊಸ ಸಿಮ್ ಸೇರಿಸಲಾಗಿದೆ"</string>
<string name="carrier_app_notification_text" msgid="6567057546341958637">"ಇದನ್ನು ಸ್ಥಾಪಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="time_picker_dialog_title" msgid="9053376764985220821">"ಸಮಯವನ್ನು ಹೊಂದಿಸಿ"</string>
- <string name="date_picker_dialog_title" msgid="5030520449243071926">"ದಿನಾಂಕವನ್ನು ಹೊಂದಿಸಿ"</string>
+ <string name="date_picker_dialog_title" msgid="5030520449243071926">"ದಿನಾಂಕವನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
<string name="date_time_set" msgid="4603445265164486816">"ಹೊಂದಿಸು"</string>
<string name="date_time_done" msgid="8363155889402873463">"ಆಯಿತು"</string>
<string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"ಹೊಸ: "</font></string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ನಿಂದ <xliff:g id="END">%2$s</xliff:g> ವರೆಗೆ"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ಯಾವುದೇ ಕ್ಯಾಲೆಂಡರ್"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ಧ್ವನಿ ಮ್ಯೂಟ್ ಮಾಡುತ್ತಿದ್ದಾರೆ"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ ಹಾಗೂ ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾವನ್ನು ರೀಸೆಟ್ ಮಾಡುವವರೆಗೂ ಅದು ಅಸ್ಥಿರವಾಗಬಹುದು."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 59af8fb584ce..5a4768367f4e 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"발신자 번호가 기본적으로 제한됨으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한됨"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"이 앱은 16KB와 호환되지 않습니다. APK 정렬 검사에 실패했습니다. 이 앱은 페이지 크기 호환 모드를 사용하여 실행됩니다. 최상의 호환성을 위해 16KB를 지원하도록 애플리케이션을 다시 컴파일하세요. 자세한 내용은 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;를 참고하세요."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"이 앱은 16KB와 호환되지 않습니다. ELF 정렬 검사에 실패했습니다. 이 앱은 페이지 크기 호환 모드를 사용하여 실행됩니다. 최상의 호환성을 위해 16KB를 지원하도록 애플리케이션을 다시 컴파일하세요. 자세한 내용은 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;를 참고하세요."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"이 앱은 16KB와 호환되지 않습니다. APK 및 ELF 정렬 검사에 실패했습니다. 이 앱은 페이지 크기 호환 모드를 사용하여 실행됩니다. 최상의 호환성을 위해 16KB를 지원하도록 애플리케이션을 다시 컴파일하세요. 자세한 내용은 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;를 참고하세요."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"서비스가 준비되지 않았습니다."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"발신자 번호 설정을 변경할 수 없습니다."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> 이동통신사로 데이터가 변경됨"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>시간 후"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>일 후"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>년 후"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{#분 전}other{#분 전}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{#시간 전}other{#시간 전}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{#일 전}other{#일 전}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"모든 캘린더"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index cbb0a13963b3..2ca59918c408 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Номурду аныктоонун демейки абалы \"чектелген\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелген"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. APK\'дин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. ELF\'тин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. APK менен ELF\'тин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары параметрлерин өзгөртө албайсыз."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилдик Интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g> которулду"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> с. кийин"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> к. кийин"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> ж. кийин"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# мүнөт мурун}other{# мүнөт мурун}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# саат мурун}other{# саат мурун}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# күн мурун}other{# күн мурун}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Бардык жылнаамалар"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> айрым үндөрдү өчүрүүдө"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Түзмөгүңүздө ички көйгөй бар жана ал баштапкы абалга кайтарылмайынча туруктуу иштебей коюшу мүмкүн."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 5af4a84c7caf..3b234e8be655 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID ໂດຍເລີ່ມຕົ້ນຖືກປັບໃຫ້ບໍ່ມີການປິດກັ້ນ. ການໂທຕໍ່ໄປ:ປິດກັ້ນ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ APK ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ ELF ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ APK ແລະ ELF ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ປ່ຽນໄປໃຊ້ອິນເຕີເນັດມືຖືຂອງ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ແລ້ວ"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"ໃນ <xliff:g id="COUNT">%d</xliff:g>ຊມ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"ໃນ <xliff:g id="COUNT">%d</xliff:g>ມ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"ໃນ <xliff:g id="COUNT">%d</xliff:g>ປ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# ນາທີກ່ອນ}other{# ນາທີກ່ອນ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ຊົ່ວໂມງກ່ອນ}other{# ຊົ່ວໂມງກ່ອນ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ມື້ກ່ອນ}other{# ມື້ກ່ອນ}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ຫາ <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ປະ​ຕິ​ທິນ​ໃດ​ກໍໄດ້"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ປິດສຽງບາງຢ່າງໄວ້"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ມີ​ບັນ​ຫາ​ພາຍ​ໃນ​ກັບ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ, ແລະ​ມັນ​ອາດ​ຈະ​ບໍ່​ສະ​ຖຽນ​ຈົນ​ກວ່າ​ທ່ານ​ຕັ້ງ​ເປັນ​ຂໍ້​ມູນ​ໂຮງ​ງານ​ຄືນ​ແລ້ວ."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index cb7cc896d42f..c1eaa041c06a 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Skambintojo ID pagal numatytuosius nustatymus yra apribotas. Kitas skambutis: neapribotas"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Skambintojo ID pagal numatytuosius nustatymus nustatomas į neapribotą. Kitas skambutis: apribotas"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Skambintojo ID pagal numatytuosius nustatymus yra neapribotas. Kitas skambutis: neapribotas"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ši programa nesuderinama su 16 KB. APK lygiavimo patikrinimas nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ši programa nesuderinama su 16 KB. ELF lygiavimo patikrinimas nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ši programa nesuderinama su 16 KB. APK ir ELF lygiavimo patikrinimai nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Paslauga neteikiama."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Negalima pakeisti skambinančiojo ID nustatymo."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Duomenys perjungti į „<xliff:g id="CARRIERDISPLAY">%s</xliff:g>“"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"po <xliff:g id="COUNT">%d</xliff:g> val."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"po <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"po <xliff:g id="COUNT">%d</xliff:g> m."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Prieš # minutę}one{Prieš # minutę}few{Prieš # minutes}many{Prieš # minutės}other{Prieš # minučių}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Prieš # valandą}one{Prieš # valandą}few{Prieš # valandas}many{Prieš # valandos}other{Prieš # valandų}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Prieš # dieną}one{Prieš # dieną}few{Prieš # dienas}many{Prieš # dienos}other{Prieš # dienų}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bet kuris kalendorius"</string>
<string name="muted_by" msgid="91464083490094950">"„<xliff:g id="THIRD_PARTY">%1$s</xliff:g>“ nutildo kai kuriuos garsus"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Iškilo vidinė su jūsų įrenginiu susijusi problema, todėl įrenginys gali veikti nestabiliai, kol neatkursite gamyklinių duomenų."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index b72fb074ab14..03c7c3c65cb7 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zvanītāja ID noklusējumi ir iestatīti uz Ierobežots. Nākamais zvans: nav ierobežots"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: ierobežots"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: nav ierobežots"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Šī lietotne nav saderīga ar 16 KB lapām. APK saskaņošanas pārbaude neizdevās. Šī lietotne tiks palaista, izmantojot ar lapas izmēru saderīgu režīmu. Lai uzlabotu saderību, lūdzu, atkārtoti kompilējiet lietojumprogrammu, nodrošinot atbalstu 16 KB lapām. Plašāku informāciju skatiet vietnē &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Šī lietotne nav saderīga ar 16 KB lapām. ELF saskaņošanas pārbaude neizdevās. Šī lietotne tiks palaista, izmantojot ar lapas izmēru saderīgu režīmu. Lai uzlabotu saderību, lūdzu, atkārtoti kompilējiet lietojumprogrammu, nodrošinot atbalstu 16 KB lapām. Plašāku informāciju skatiet vietnē &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Šī lietotne nav saderīga ar 16 KB lapām. APK un ELF saskaņošanas pārbaudes neizdevās. Šī lietotne tiks palaista, izmantojot ar lapas izmēru saderīgu režīmu. Lai uzlabotu saderību, lūdzu, atkārtoti kompilējiet lietojumprogrammu, nodrošinot atbalstu 16 KB lapām. Plašāku informāciju skatiet vietnē &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Pakalpojums netiek nodrošināts."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Zvanītāja ID iestatījumu nevar mainīt."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Tiek izmantots operatora <xliff:g id="CARRIERDISPLAY">%s</xliff:g> datu savienojums"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"pēc <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"pēc <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"pēc <xliff:g id="COUNT">%d</xliff:g> g."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Pirms vienas minūtes}zero{Pirms # minūtēm}one{Pirms vairākām minūtēm, minūšu skaits: #}other{Pirms vairākām minūtēm, minūšu skaits: #}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Pirms vienas stundas}zero{Pirms # stundām}one{Pirms vairākām stundām, stundu skaits: #}other{Pirms vairākām stundām, stundu skaits: #}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Pirms vienas dienas}zero{Pirms # dienām}one{Pirms vairākām dienām, dienu skaits: #}other{Pirms vairākām dienām, dienu skaits: #}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"no <xliff:g id="START">%1$s</xliff:g> līdz <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Jebkurš kalendārs"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izslēdz noteiktas skaņas"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Jūsu ierīcē ir radusies iekšēja problēma, un ierīce var darboties nestabili. Lai to labotu, veiciet rūpnīcas datu atiestatīšanu."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0b0e3ef81cca..e299c8b5a7a8 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Стандардно, ID на повикувач е скриен. Следен повик: не е скриен"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Стандардно, ID на повикувач не е скриен. Следен повик: скриен"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Апликацијава не е компатибилна со 16 KB. Проверката за усогласување на АПК не успеа. Апликацијава ќе се извршува со режим компатибилен со големината на страницата. За најдобра компатибилност, рекомпилирајте ја апликацијата со поддршка за 16 KB. За повеќе информации, одете на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Апликацијава не е компатибилна со 16 KB. Проверката за усогласување на ELF не успеа. Апликацијава ќе се извршува со режим компатибилен со големината на страницата. За најдобра компатибилност, рекомпилирајте ја апликацијата со поддршка за 16 KB. За повеќе информации, одете на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Апликацијава не е компатибилна со 16 KB. Проверките за усогласување на АПК и ELF се неуспешни. Апликацијава ќе се извршува со режим компатибилен со големината на страницата. За најдобра компатибилност, рекомпилирајте ја апликацијата со поддршка за 16 KB. За повеќе информации, одете на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилниот интернет се префрли на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"по <xliff:g id="COUNT">%d</xliff:g> ч."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"по <xliff:g id="COUNT">%d</xliff:g> д."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"по <xliff:g id="COUNT">%d</xliff:g> г."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Пред # минута}one{Пред # минута}other{Пред # минути}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Пред # час}one{Пред # час}other{Пред # часа}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Пред # ден}one{Пред # ден}other{Пред # дена}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> до <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Кој било календар"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> исклучи некои звуци"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Настана внатрешен проблем со уредот и може да биде нестабилен сè додека не ресетирате на фабричките податоци."</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index fe5974b3df24..3c08c013d3e3 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"നിയന്ത്രിക്കേണ്ട സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടുണ്ട്"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ഈ ആപ്പ് 16 KB-ക്ക് അനുയോജ്യമല്ല. APK അലൈൻമെന്റ് പരിശോധന നടത്താനായില്ല. പേജ് വലുപ്പത്തിന് അനുയോജ്യമായ മോഡ് ഉപയോഗിച്ച് ഈ ആപ്പ് റൺ ചെയ്യും. മികച്ച അനുയോജ്യതയ്ക്കായി, 16 KB പിന്തുണയോടെ ആപ്പ് വീണ്ടും കംപൈൽ ചെയ്യുക. കൂടുതൽ വിവരങ്ങൾക്ക്, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; കാണുക"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ഈ ആപ്പ് 16 KB-ക്ക് അനുയോജ്യമല്ല. ELF അലൈൻമെന്റ് പരിശോധന നടത്താനായില്ല. പേജ് വലുപ്പത്തിന് അനുയോജ്യമായ മോഡ് ഉപയോഗിച്ച് ഈ ആപ്പ് റൺ ചെയ്യും. മികച്ച അനുയോജ്യതയ്ക്കായി, 16 KB പിന്തുണയോടെ ആപ്പ് വീണ്ടും കംപൈൽ ചെയ്യുക. കൂടുതൽ വിവരങ്ങൾക്ക്, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; കാണുക"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ഈ ആപ്പ് 16 KB-ക്ക് അനുയോജ്യമല്ല. APK, ELF അലൈൻമെന്റ് പരിശോധനകൾ നടത്താനായില്ല. പേജ് വലുപ്പത്തിന് അനുയോജ്യമായ മോഡ് ഉപയോഗിച്ച് ഈ ആപ്പ് റൺ ചെയ്യും. മികച്ച അനുയോജ്യതയ്ക്കായി, 16 KB പിന്തുണയോടെ ആപ്പ് വീണ്ടും കംപൈൽ ചെയ്യുക. കൂടുതൽ വിവരങ്ങൾക്ക്, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; കാണുക"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"സേവനം വ്യവസ്ഥ ചെയ്‌തിട്ടില്ല."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"വിളിച്ച നമ്പർ ക്രമീകരണം നിങ്ങൾക്ക് മാറ്റാനാവില്ല."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> എന്നതിലേക്ക് ഡാറ്റ മാറ്റി"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>മണിക്കൂറിൽ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>ദിവസത്തിൽ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>വർഷത്തിനുള്ളിൽ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# മിനിറ്റ് മുമ്പ്}other{# മിനിറ്റ് മുമ്പ്}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# മണിക്കൂർ മുമ്പ്}other{# മണിക്കൂർ മുമ്പ്}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ദിവസം മുമ്പ്}other{# ദിവസം മുമ്പ്}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> മുതൽ <xliff:g id="END">%2$s</xliff:g> വരെ"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"എല്ലാ കലണ്ടറിലും"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ചില ശബ്‌ദങ്ങൾ മ്യൂട്ട് ചെയ്യുന്നു"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്‌നമുണ്ട്, ഫാക്‌ടറി വിവര പുനഃസജ്ജീകരണം ചെയ്യുന്നതുവരെ ഇതു അസ്ഥിരമായിരിക്കാനിടയുണ്ട്."</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 376a03350b23..e32eff0f91a1 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдаагүй"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Дуудлага хийгчийн ID хязгаарлагдаагүй. Дараагийн дуудлага: Хязгаарлагдсан"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Энэ апп 16 КБ-ын хэмжээтэй хуудастай тохиромжгүй байна. APK эгнүүлэлтийн шалгалт амжилтгүй боллоо. Энэ апп хуудасны хэмжээтэй тохирох горимыг ашиглан ажиллана. Хамгийн тохиромжтой байлгахын тулд 16 КБ-ын дэмжлэгээр аппликэйшнийг дахин хөрвүүлнэ үү. Нэмэлт мэдээлэл авах бол &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; холбоосыг харна уу"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Энэ апп 16 КБ-ын хэмжээтэй хуудастай тохиромжгүй байна. ELF эгнүүлэлтийн шалгалт амжилтгүй боллоо. Энэ апп хуудасны хэмжээтэй тохирох горимыг ашиглан ажиллана. Хамгийн тохиромжтой байлгахын тулд 16 КБ-ын дэмжлэгээр аппликэйшнийг дахин хөрвүүлнэ үү. Нэмэлт мэдээлэл авах бол &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; холбоосыг харна уу"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Энэ апп 16 КБ-ын хэмжээтэй хуудастай тохиромжгүй байна. APK, ELF эгнүүлэлтийн шалгалт амжилтгүй боллоо. Энэ апп хуудасны хэмжээтэй тохирох горимыг ашиглан ажиллана. Хамгийн тохиромжтой байлгахын тулд 16 КБ-ын дэмжлэгээр аппликэйшнийг дахин хөрвүүлнэ үү. Нэмэлт мэдээлэл авах бол &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; холбоосыг харна уу"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Өгөгдлийг <xliff:g id="CARRIERDISPLAY">%s</xliff:g> руу шилжүүлсэн"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>цагийн дараа"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>хоногийн дараа"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>жилийн дараа"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# минутын өмнө}other{# минутын өмнө}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# цагийн өмнө}other{# цагийн өмнө}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# хоногийн өмнө}other{# хоногийн өмнө}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-с <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Дурын календарь"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index e640f729d093..abe9a948a063 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. APK अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. ELF अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. APK आणि ELF अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवेची तरतूद केलेली नाही."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"तुम्ही कॉलर आयडी सेटिंग बदलू शकत नाही."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा <xliff:g id="CARRIERDISPLAY">%s</xliff:g> वर स्विच केला"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> तासांमध्ये"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> दिवसांमध्ये"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> वर्षांमध्ये"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# मिनिटापूर्वी}other{# मिनिटांपूर्वी}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# तासापूर्वी}other{# तासांपूर्वी}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# दिवसापूर्वी}other{# दिवसांपूर्वी}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ते <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"कोणतेही कॅलेंडर"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> काही ध्‍वनी म्‍यूट करत आहे"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"आपल्‍या डिव्‍हाइसमध्‍ये अंतर्गत समस्‍या आहे आणि तुमचा फॅक्‍टरी डेटा रीसेट होईपर्यंत ती अस्‍थिर असू शकते."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3946ac1df42f..1fea24358ad4 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID pemanggil secara lalainya ditetapkan kepada terhad. Panggilan seterusnya: Tidak terhad"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID pemanggil secara lalainya ditetapkan kepada tidak terhad. Panggilan seterusnya: Terhad"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pemanggil secara lalainya ditetapkan kepada tidak dihadkan. Panggilan seterusnya: Tidak terhad"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Apl ini tidak serasi untuk 16 KB. Semakan penjajaran APK gagal. Apl ini akan dijalankan menggunakan mod serasi saiz halaman. Untuk keserasian yang terbaik, sila susun semula aplikasi dengan sokongan 16 KB. Untuk mendapatkan maklumat lanjut, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Apl ini tidak serasi untuk 16 KB. Semakan penjajaran ELF gagal. Apl ini akan dijalankan menggunakan mod serasi saiz halaman. Untuk keserasian yang terbaik, sila susun semula aplikasi dengan sokongan 16 KB. Untuk mendapatkan maklumat lanjut, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Apl ini tidak serasi untuk 16 KB. Semakan penjajaran APK dan ELF gagal. Apl ini akan dijalankan menggunakan mod serasi saiz halaman. Untuk keserasian yang terbaik, sila susun semula aplikasi dengan sokongan 16 KB. Untuk mendapatkan maklumat lanjut, lihat &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Perkhidmatan yang tidak diuntukkan."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak boleh mengubah tetapan ID pemanggil."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Data ditukar kepada <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"dalam <xliff:g id="COUNT">%d</xliff:g>j"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"dalam <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"dalam <xliff:g id="COUNT">%d</xliff:g>t"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minit yang lalu}other{# minit yang lalu}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# jam yang lalu}other{# jam yang lalu}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# hari yang lalu}other{# hari yang lalu}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hingga <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Sebarang kalendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> meredamkan sesetengah bunyi"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Terdapat masalah dalaman dengan peranti anda. Peranti mungkin tidak stabil sehingga anda membuat tetapan semula data kilang."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fc0072557cb0..ec6eff427a19 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်ထားသည်။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်ထားသည်။"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ဤအက်ပ်သည် ၁၆ KB နှင့် တွဲမသုံးနိုင်ပါ။ APK ချိန်ညှိခြင်း စစ်ဆေးမှု မအောင်မြင်ပါ။ ဤအက်ပ်သည် တွဲသုံးနိုင်သော စာမျက်နှာအရွယ်အစားမုဒ်သုံး၍ လုပ်ဆောင်ပါမည်။ အကောင်းဆုံး တွဲသုံးနိုင်မှုအတွက် အပလီကေးရှင်းကို ၁၆ KB ပံ့ပိုးမှုဖြင့် ပြန်လည်တည်ဆောက်ပါ။ နောက်ထပ်အချက်အလက်အတွက် &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; တွင် ကြည့်ပါ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ဤအက်ပ်သည် ၁၆ KB နှင့် တွဲမသုံးနိုင်ပါ။ ELF ချိန်ညှိခြင်း စစ်ဆေးမှု မအောင်မြင်ပါ။ ဤအက်ပ်သည် တွဲသုံးနိုင်သော စာမျက်နှာအရွယ်အစားမုဒ်သုံး၍ လုပ်ဆောင်ပါမည်။ အကောင်းဆုံး တွဲသုံးနိုင်မှုအတွက် အပလီကေးရှင်းကို ၁၆ KB ပံ့ပိုးမှုဖြင့် ပြန်လည်တည်ဆောက်ပါ။ နောက်ထပ်အချက်အလက်အတွက် &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; တွင် ကြည့်ပါ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ဤအက်ပ်သည် ၁၆ KB နှင့် တွဲမသုံးနိုင်ပါ။ APK နှင့် ELF ချိန်ညှိခြင်း စစ်ဆေးမှု မအောင်မြင်ပါ။ ဤအက်ပ်သည် တွဲသုံးနိုင်သော စာမျက်နှာအရွယ်အစားမုဒ်သုံး၍ လုပ်ဆောင်ပါမည်။ အကောင်းဆုံး တွဲသုံးနိုင်မှုအတွက် အပလီကေးရှင်းကို ၁၆ KB ပံ့ပိုးမှုဖြင့် ပြန်လည်တည်ဆောက်ပါ။ နောက်ထပ်အချက်အလက်အတွက် &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; တွင် ကြည့်ပါ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ဒေတာကို <xliff:g id="CARRIERDISPLAY">%s</xliff:g> သို့ ပြောင်းထားသည်"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> နာရီအတွင်း"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> ရက်အတွင်း"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> နှစ်အတွင်း"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{ပြီးခဲ့သော # မိနစ်}other{ပြီးခဲ့သော # မိနစ်}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{ပြီးခဲ့သော # နာရီ}other{ပြီးခဲ့သော # နာရီ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{ပြီးခဲ့သော # ရက်}other{ပြီးခဲ့သော # ရက်}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"၊ "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> မှ <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>၊ <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"မည်သည့်ပြက္ခဒိန်မဆို"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> သည် အချို့အသံကို ပိတ်နေသည်"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေပြီး၊ မူလစက်ရုံထုတ်အခြေအနေအဖြစ် ပြန်လည်ရယူနိုင်သည်အထိ အခြေအနေမတည်ငြိမ်နိုင်ပါ။"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index c1095d3978ef..fd80ab4653b8 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nummervisning er begrenset som standard. Neste anrop: Ikke begrenset"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Nummervisning er ikke begrenset som standard. Neste anrop: Begrenset"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Denne appen er ikke kompatibel med 16 kB. Kontrollen av APK-justering mislyktes. Denne appen kjøres i modus for sideformatkompatibilitet. For å få best mulig kompatibilitet bør du kompilere appen på nytt med støtte for 16 kB. Du finner mer informasjon på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Denne appen er ikke kompatibel med 16 kB. Kontrollen av ELF-justering mislyktes. Denne appen kjøres i modus for sideformatkompatibilitet. For å få best mulig kompatibilitet bør du kompilere appen på nytt med støtte for 16 kB. Du finner mer informasjon på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Denne appen er ikke kompatibel med 16 kB. APK- og ELF-justeringskontrollene mislyktes. Denne appen kjøres i modus for sideformatkompatibilitet. For å få best mulig kompatibilitet bør du kompilere appen på nytt med støtte for 16 kB. Du finner mer informasjon på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"SIM-kortet er ikke tilrettelagt for tjenesten."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke endre innstillingen for anrops-ID."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Byttet data til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"om <xliff:g id="COUNT">%d</xliff:g> t"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"om <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"om <xliff:g id="COUNT">%d</xliff:g> år"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{for # minutt siden}other{For # minutter siden}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# time siden}other{# timer siden}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{For # dag siden}other{For # dager siden}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Hvilken som helst kalender"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår av noen lyder"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Det har oppstått et internt problem på enheten din, og den kan være ustabil til du tilbakestiller den til fabrikkdata."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index beda131e3de4..6fabb068759e 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कलर ID पूर्वनिर्धारितको लागि रोकावट छ। अर्को कल: रोकावट छैन"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कलर ID पूर्वनिर्धारितदेखि प्रतिबन्धित छैन। अर्को कल: प्रतिबन्धित छ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"यो एप १६ के.बि. को पेजसँग कम्प्याटिबल छैन। APK को एलाइनमेन्ट जाँच्न सकिएन। यो एप पेजको साइजसँग कम्प्याटिबल मोड प्रयोग गरेर चलाइने छ। उत्कृष्ट कम्प्याटिबिलिटीका लागि कृपया यो एप १६ के.बि. को पेज प्रयोग गर्न मिल्ने गरी रिकम्पलाइल गर्नुहोस्। यस सम्बन्धमा थप जानकारी प्राप्त गर्न &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; हेर्नुहोस्"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"यो एप १६ के.बि. को पेजसँग कम्प्याटिबल छैन। ELF को एलाइनमेन्ट जाँच्न सकिएन। यो एप पेजको साइजसँग कम्प्याटिबल मोड प्रयोग गरेर चलाइने छ। उत्कृष्ट कम्प्याटिबिलिटीका लागि कृपया यो एप १६ के.बि. को पेज प्रयोग गर्न मिल्ने गरी रिकम्पलाइल गर्नुहोस्। यस सम्बन्धमा थप जानकारी प्राप्त गर्न &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; हेर्नुहोस्"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"यो एप १६ के.बि. को पेजसँग कम्प्याटिबल छैन। APK र ELF को एलाइनमेन्ट जाँच्न सकिएन। यो एप पेजको साइजसँग कम्प्याटिबल मोड प्रयोग गरेर चलाइने छ। उत्कृष्ट कम्प्याटिबिलिटीका लागि कृपया यो एप १६ के.बि. को पेज प्रयोग गर्न मिल्ने गरी रिकम्पलाइल गर्नुहोस्। यस सम्बन्धमा थप जानकारी प्राप्त गर्न &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; हेर्नुहोस्"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> को डेटा प्रयोग गर्न थालिएको छ"</string>
@@ -1105,7 +1108,7 @@
<string name="permlab_addVoicemail" msgid="4770245808840814471">"भ्वाइसमेल थप गर्नुहोस्"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"तपाईँको भ्वाइसमेल इनबक्समा सन्देश थप्नको लागि एपलाई अनुमति दिन्छ।"</string>
<string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> ले तपाईंको क्लिपबोर्डमा रहेको जानकारी पेस्ट गरेको छ"</string>
- <string name="more_item_label" msgid="7419249600215749115">"बढी"</string>
+ <string name="more_item_label" msgid="7419249600215749115">"थप"</string>
<string name="prepend_shortcut_label" msgid="1743716737502867951">"मेनु+"</string>
<string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
<string name="menu_ctrl_shortcut_label" msgid="131911133027196485">"Ctrl+"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> घण्टाभित्र"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> दिनभित्र"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> वर्षभित्र"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# मिनेटअघि}other{# मिनेटअघि}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# घण्टाअघि}other{# घण्टाअघि}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# दिनअघि}other{# दिनअघि}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> देखि <xliff:g id="END">%2$s</xliff:g> सम्म"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"कुनै पनि पात्रो"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ले केही ध्वनिहरू म्युट गर्दै छ"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ, र तपाईंले फ्याक्ट्री डाटा रिसेट नगर्दासम्म यो अस्थिर रहन्छ।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 48c8be196bc0..b93b916997be 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Beller-ID standaard ingesteld op \'beperkt\'. Volgend gesprek: onbeperkt."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: beperkt."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: onbeperkt."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Deze app is niet compatibel met 16 KB. APK-uitlijningscontrole mislukt. Deze app wordt uitgevoerd in de compatibele modus voor paginagrootte. Voor de beste compatibiliteit moet je de app opnieuw compileren met ondersteuning voor 16 KB. Zie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; voor meer informatie."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Deze app is niet compatibel met 16 KB. ELF-uitlijningscontrole mislukt. Deze app wordt uitgevoerd in de compatibele modus voor paginagrootte. Voor de beste compatibiliteit moet je de app opnieuw compileren met ondersteuning voor 16 KB. Zie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; voor meer informatie."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Deze app is niet compatibel met 16 KB. APK- en ELF-uitlijningscontroles mislukt. Deze app wordt uitgevoerd in de compatibele modus voor paginagrootte. Voor de beste compatibiliteit moet je de app opnieuw compileren met ondersteuning voor 16 KB. Zie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; voor meer informatie."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Service niet voorzien."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"U kunt de instelling voor de beller-ID niet wijzigen."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiele data overgeschakeld naar <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"over <xliff:g id="COUNT">%d</xliff:g> u"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"over <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"over <xliff:g id="COUNT">%d</xliff:g> j"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuut geleden}other{# minuten geleden}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# uur geleden}other{# uur geleden}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# dag geleden}other{# dagen geleden}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> tot <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Elke agenda"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> zet sommige geluiden uit"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Er is een intern probleem met je apparaat. Het apparaat kan instabiel zijn totdat u het apparaat terugzet naar de fabrieksinstellingen."</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index a99afa5a0a2f..961822a86bd7 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ଏହି ଆପ 16 KB କମ୍ପାଟିବଲ ନୁହେଁ। APK ଆଲାଇନମେଣ୍ଟ ଯାଞ୍ଚ ବିଫଳ ହୋଇଛି। ପୃଷ୍ଠା ସାଇଜ କମ୍ପାଟିବଲ ମୋଡ ବ୍ୟବହାର କରି ଏହି ଆପକୁ ଚଲାଯିବ। ସର୍ବୋତ୍ତମ କମ୍ପାଟିବିଲିଟୀ ପାଇଁ ଦୟାକରି 16 KB ସପୋର୍ଟ ସହ ଆପ୍ଲିକେସନକୁ ପୁଣି କମ୍ପାଇଲ କରନ୍ତୁ। ଅଧିକ ସୂଚନା ପାଇଁ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;କୁ ଦେଖନ୍ତୁ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ଏହି ଆପ 16 KB କମ୍ପାଟିବଲ ନୁହେଁ। ELF ଆଲାଇନମେଣ୍ଟ ଯାଞ୍ଚ ବିଫଳ ହୋଇଛି। ପୃଷ୍ଠା ସାଇଜ କମ୍ପାଟିବଲ ମୋଡ ବ୍ୟବହାର କରି ଏହି ଆପକୁ ଚଲାଯିବ। ସର୍ବୋତ୍ତମ କମ୍ପାଟିବିଲିଟୀ ପାଇଁ ଦୟାକରି 16 KB ସପୋର୍ଟ ସହ ଆପ୍ଲିକେସନକୁ ପୁଣି କମ୍ପାଇଲ କରନ୍ତୁ। ଅଧିକ ସୂଚନା ପାଇଁ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;କୁ ଦେଖନ୍ତୁ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ଏହି ଆପ 16 KB କମ୍ପାଟିବଲ ନୁହେଁ। APK ଏବଂ ELF ଆଲାଇନମେଣ୍ଟ ଯାଞ୍ଚଗୁଡ଼ିକ ବିଫଳ ହୋଇଛି। ପୃଷ୍ଠା ସାଇଜ କମ୍ପାଟିବଲ ମୋଡ ବ୍ୟବହାର କରି ଏହି ଆପକୁ ଚଲାଯିବ। ସର୍ବୋତ୍ତମ କମ୍ପାଟିବିଲିଟୀ ପାଇଁ ଦୟାକରି 16 KB ସପୋର୍ଟ ସହ ଆପ୍ଲିକେସନକୁ ପୁଣି କମ୍ପାଇଲ କରନ୍ତୁ। ଅଧିକ ସୂଚନା ପାଇଁ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;କୁ ଦେଖନ୍ତୁ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍‍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>କୁ ଡାଟା ସ୍ୱିଚ କରାଯାଇଛି"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> ଘଣ୍ଟାରେ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> ଦିନରେ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> ବର୍ଷରେ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# ମିନିଟ ପୂର୍ବେ}other{# ମିନିଟ ପୂର୍ବେ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ଘଣ୍ଟା ପୂର୍ବେ}other{# ଘଣ୍ଟା ପୂର୍ବେ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ଦିନ ପୂର୍ବେ}other{# ଦିନ ପୂର୍ବେ}}"</string>
@@ -1665,7 +1700,7 @@
<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="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ"</string>
+ <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"ବ୍ଲୁଟୁଥ ଅଡିଓ"</string>
<string name="wireless_display_route_description" msgid="8297563323032966831">"ୱେୟାର୍‍ଲେସ୍‍ ଡିସ୍‍ପ୍ଲେ"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"କାଷ୍ଟ କରନ୍ତୁ"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"ଡିଭାଇସ୍‍ ସଂଯୋଗ କରନ୍ତୁ"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ରୁ <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ଯେକୌଣସି କ୍ୟାଲେଣ୍ଡର୍‌"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> କିଛି ସାଉଣ୍ଡକୁ ମ୍ୟୁଟ୍ କରୁଛି"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ଆପଣଙ୍କ ଡିଭାଇସ୍‍ରେ ଏକ ସମସ୍ୟା ରହିଛି ଏବଂ ଆପଣ ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଅସ୍ଥିର ରହିପାରେ।"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b46301744701..238354adbb66 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ਪ੍ਰਤਿਬੰਧਿਤ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ਇਹ ਐਪ 16 KB ਦੇ ਅਨੁਰੂਪ ਨਹੀਂ ਹੈ। APK ਦੀ ਇਕਸਾਰਤਾ ਦੀ ਜਾਂਚ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ। ਇਹ ਐਪ ਪੰਨੇ ਦੇ ਆਕਾਰ ਦੇ ਅਨੁਰੂਪ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਚੱਲੇਗੀ। ਬਿਹਤਰ ਅਨੁਰੂਪਤਾ ਲਈ, ਕਿਰਪਾ ਕਰਕੇ 16 KB ਵਾਲੇ ਪੰਨਿਆਂ ਨਾਲ ਐਪਲੀਕੇਸ਼ਨ ਦਾ ਮੁੜ-ਸੰਕਲਨ ਕਰੋ। ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ਦੇਖੋ"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ਇਹ ਐਪ 16 KB ਦੇ ਅਨੁਰੂਪ ਨਹੀਂ ਹੈ। ELF ਦੀ ਇਕਸਾਰਤਾ ਦੀ ਜਾਂਚ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ। ਇਹ ਐਪ ਪੰਨੇ ਦੇ ਆਕਾਰ ਦੇ ਅਨੁਰੂਪ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਚੱਲੇਗੀ। ਬਿਹਤਰ ਅਨੁਰੂਪਤਾ ਲਈ, ਕਿਰਪਾ ਕਰਕੇ 16 KB ਵਾਲੇ ਪੰਨਿਆਂ ਨਾਲ ਐਪਲੀਕੇਸ਼ਨ ਦਾ ਮੁੜ-ਸੰਕਲਨ ਕਰੋ। ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ਦੇਖੋ"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ਇਹ ਐਪ 16 KB ਦੇ ਅਨੁਰੂਪ ਨਹੀਂ ਹੈ। APK ਅਤੇ ELF ਦੀ ਇਕਸਾਰਤਾ ਦੀ ਜਾਂਚ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ। ਇਹ ਐਪ ਪੰਨੇ ਦੇ ਆਕਾਰ ਦੇ ਅਨੁਰੂਪ ਮੋਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਚੱਲੇਗੀ। ਬਿਹਤਰ ਅਨੁਰੂਪਤਾ ਲਈ, ਕਿਰਪਾ ਕਰਕੇ 16 KB ਵਾਲੇ ਪੰਨਿਆਂ ਨਾਲ ਐਪਲੀਕੇਸ਼ਨ ਦਾ ਮੁੜ-ਸੰਕਲਨ ਕਰੋ। ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ਦੇਖੋ"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"ਤੁਸੀਂ ਕਾਲਰ ਆਈ.ਡੀ. ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ਡਾਟੇ ਨੂੰ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕੀਤਾ ਗਿਆ"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> ਘੰਟੇ ਵਿੱਚ"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> ਦਿਨ ਵਿੱਚ"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> ਸਾਲ ਵਿੱਚ"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# ਮਿੰਟ ਪਹਿਲਾਂ}one{# ਮਿੰਟ ਪਹਿਲਾਂ}other{# ਮਿੰਟ ਪਹਿਲਾਂ}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ਘੰਟਾ ਪਹਿਲਾਂ}one{# ਘੰਟਾ ਪਹਿਲਾਂ}other{# ਘੰਟੇ ਪਹਿਲਾਂ}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ਦਿਨ ਪਹਿਲਾਂ}one{# ਦਿਨ ਪਹਿਲਾਂ}other{# ਦਿਨ ਪਹਿਲਾਂ}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ਤੋਂ <xliff:g id="END">%2$s</xliff:g> ਤੱਕ"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ਕੋਈ ਵੀ ਕੈਲੰਡਰ"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ਕੁਝ ਧੁਨੀਆਂ ਨੂੰ ਮਿਊਟ ਕਰ ਰਹੀ ਹੈ"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਹੈ ਅਤੇ ਇਹ ਅਸਥਿਰ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਨਹੀਂ ਕਰਦੇ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 4f34fc5ed84e..3d71d7baeaca 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID rozmówcy ustawiony jest domyślnie na „zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: zastrzeżony"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ta aplikacja nie jest zgodna z trybem 16 KB. Sprawdzenie zgodności pliku APK się nie powiodło. Ta aplikacja będzie działać w trybie zgodnym z rozmiarem strony. Aby zapewnić najlepszą zgodność, rekompiluj aplikację, żeby obsługiwała 16 KB. Więcej informacji znajdziesz na stronie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ta aplikacja nie jest zgodna z trybem 16 KB. Sprawdzenie zgodności ELF się nie powiodło. Ta aplikacja będzie działać w trybie zgodnym z rozmiarem strony. Aby zapewnić najlepszą zgodność, rekompiluj aplikację, żeby obsługiwała 16 KB. Więcej informacji znajdziesz na stronie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ta aplikacja nie jest zgodna z trybem 16 KB. Sprawdzenie zgodności pliku APK i ELF się nie powiodło. Ta aplikacja będzie działać w trybie zgodnym z rozmiarem strony. Aby zapewnić najlepszą zgodność, rekompiluj aplikację, żeby obsługiwała 16 KB. Więcej informacji znajdziesz na stronie &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Usługa nie jest świadczona."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nie możesz zmienić ustawienia ID rozmówcy."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Przełączono mobilną transmisję danych na: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> godz."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> r."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minutę temu}few{# minuty temu}many{# minut temu}other{# minuty temu}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# godzinę temu}few{# godziny temu}many{# godzin temu}other{# godziny temu}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# dzień temu}few{# dni temu}many{# dni temu}other{# dnia temu}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Od <xliff:g id="START">%1$s</xliff:g> do <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Dowolny kalendarz"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> wycisza niektóre dźwięki"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"W Twoim urządzeniu wystąpił problem wewnętrzny. Może być ono niestabilne, dopóki nie przywrócisz danych fabrycznych."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index a2192767258a..8e943db600d1 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O identificador de chamadas assume o padrão de restrito. Próxima chamada: Não restrita"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Restrita"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Este app não é compatível com 16 KB. Falha na verificação de alinhamento do APK. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Este app não é compatível com 16 KB. Falha na verificação de alinhamento ELF. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Este app não é compatível com 16 KB. Falha nas verificações de alinhamento do APK e do ELF. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"em <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"em <xliff:g id="COUNT">%d</xliff:g> dias"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"em <xliff:g id="COUNT">%d</xliff:g>a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuto atrás}one{# minuto atrás}many{# minutos atrás}other{# minutos atrás}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hora atrás}one{# hora atrás}many{# horas atrás}other{# horas atrás}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# dia atrás}one{# dia atrás}many{# dias atrás}other{# dias atrás}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer agenda"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index a898121434e5..b5ee46060865 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID do autor da chamada é predefinido como restrito. Chamada seguinte: Não restrita"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID do autor da chamada é predefinido como não restrito. Chamada seguinte: Restrita"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID do autor da chamada é predefinido com não restrito. Chamada seguinte: Não restrita"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Esta app não é compatível com 16 KB. Falha na verificação do alinhamento do APK. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Esta app não é compatível com 16 KB. Falha na verificação do alinhamento do ELF. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Esta app não é compatível com 16 KB. As verificações de alinhamento do APK e do ELF falharam. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Serviço não fornecido."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Não pode alterar a definição da identificação de chamadas."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Os dados móveis foram alterados para o operador <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"em <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"em <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"em <xliff:g id="COUNT">%d</xliff:g> a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Há # minuto}many{Há # minutos}other{Há # minutos}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{há # hora}many{há # horas}other{há # horas}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Há # dia}many{Há # dias}other{Há # dias}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer calendário"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index a2192767258a..8e943db600d1 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O identificador de chamadas assume o padrão de restrito. Próxima chamada: Não restrita"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Restrita"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Este app não é compatível com 16 KB. Falha na verificação de alinhamento do APK. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Este app não é compatível com 16 KB. Falha na verificação de alinhamento ELF. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Este app não é compatível com 16 KB. Falha nas verificações de alinhamento do APK e do ELF. Este app será executado usando o modo compatível com o tamanho da página. Para melhorar a compatibilidade, é preciso recompilar o aplicativo com suporte a 16 KB. Para mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"em <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"em <xliff:g id="COUNT">%d</xliff:g> dias"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"em <xliff:g id="COUNT">%d</xliff:g>a"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuto atrás}one{# minuto atrás}many{# minutos atrás}other{# minutos atrás}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hora atrás}one{# hora atrás}many{# horas atrás}other{# horas atrás}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# dia atrás}one{# dia atrás}many{# dias atrás}other{# dias atrás}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer agenda"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 8b7a29683d8e..79bfcf49c1c5 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: Restricționat."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Această aplicație nu este compatibilă cu 16 KB. Verificarea alinierii APK nu a reușit. Aplicația va rula folosind modul compatibil cu dimensiunea paginii. Pentru cea mai bună compatibilitate, recompilează aplicația cu suport pentru 16 KB. Pentru mai multe informații, accesează &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Această aplicație nu este compatibilă cu 16 KB. Verificarea alinierii ELF nu a reușit. Aplicația va rula folosind modul compatibil cu dimensiunea paginii. Pentru cea mai bună compatibilitate, recompilează aplicația cu suport pentru 16 KB. Pentru mai multe informații, accesează &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Această aplicație nu este compatibilă cu 16 KB. Verificările de aliniere APK și ELF nu au reușit. Aplicația va rula folosind modul compatibil cu dimensiunea paginii. Pentru cea mai bună compatibilitate, recompilează aplicația cu suport pentru 16 KB. Pentru mai multe informații, accesează &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"S-a trecut la datele mobile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"în <xliff:g id="COUNT">%d</xliff:g> ore"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"în <xliff:g id="COUNT">%d</xliff:g> z"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"în <xliff:g id="COUNT">%d</xliff:g> ani"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Acum # minut}few{Acum # minute}other{Acum # de minute}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Acum # oră}few{Acum # ore}other{Acum # de ore}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Acum # zi}few{Acum # zile}other{Acum # de zile}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Orice calendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 386830e83e66..2631dabe06fb 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Идентификация абонента по умолчанию запрещена. След. вызов: разрешена"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Идентификация абонента по умолчанию не запрещена. След. вызов: запрещена"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Идентификация абонента по умолчанию не запрещена. След. вызов: разрешена"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Это приложение не поддерживает страничную память объемом 16 КБ. Не удалось проверить выравнивание данных в APK-файле. Приложение будет запущено в режиме совместимости. Чтобы улучшить совместимость, заново скомпилируйте приложение для поддержки страничной памяти объемом 16 КБ. Подробнее: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Это приложение не поддерживает страничную память объемом 16 КБ. Не удалось проверить выравнивание данных в ELF-файле. Приложение будет запущено в режиме совместимости. Чтобы улучшить совместимость, заново скомпилируйте приложение для поддержки страничной памяти объемом 16 КБ. Подробнее: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Это приложение не поддерживает страничную память объемом 16 КБ. Не удалось проверить выравнивание данных в APK- и ELF-файлах. Приложение будет запущено в режиме совместимости. Чтобы улучшить совместимость, заново скомпилируйте приложение для поддержки страничной памяти объемом 16 КБ. Подробнее: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга не предоставляется."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Невозможно изменить параметр идентификатора вызывающего абонента."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Используется мобильный интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"через <xliff:g id="COUNT">%d</xliff:g> ч."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"через <xliff:g id="COUNT">%d</xliff:g> дн."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"через <xliff:g id="COUNT">%d</xliff:g> г."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# минуту назад}one{# минуту назад}few{# минуты назад}many{# минут назад}other{# минуты назад}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# час назад}one{# час назад}few{# часа назад}many{# часов назад}other{# часа назад}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# день назад}one{# день назад}few{# дня назад}many{# дней назад}other{# дня назад}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Любой календарь"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> приглушает некоторые звуки."</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Произошла внутренняя ошибка, и устройство может работать нестабильно, пока вы не выполните сброс настроек."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index e969a8ed523f..99b925aa19ba 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී ඇත. මීළඟ ඇමතුම: සීමා කර නැත"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"මෙම යෙදුම 16 KB අනුකූල නොවේ. APK පෙළගැස්වීමේ පරීක්ෂාව අසමත් විය. මෙම යෙදුම පිටු ප්‍රමාණයට ගැළපෙන ප්‍රකාරය භාවිතයෙන් ධාවනය වනු ඇත. හොඳම ගැළපුම සඳහා, 16 KB සහාය ඇතිව යෙදුම නැවත සම්පාදනය කරන්න. වැඩිපුර තොරතුරු සඳහා, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; බලන්න"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"මෙම යෙදුම 16 KB අනුකූල නොවේ. ELF පෙළගැස්වීමේ පරීක්ෂාව අසමත් විය. මෙම යෙදුම පිටු ප්‍රමාණයට ගැළපෙන ප්‍රකාරය භාවිතයෙන් ධාවනය වනු ඇත. හොඳම ගැළපුම සඳහා, 16 KB සහාය ඇතිව යෙදුම නැවත සම්පාදනය කරන්න. වැඩිපුර තොරතුරු සඳහා, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; බලන්න"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"මෙම යෙදුම 16 KB අනුකූල නොවේ. APK සහ ELF පෙළගැස්වීමේ පරීක්ෂාවන් අසමත් විය. මෙම යෙදුම පිටු ප්‍රමාණයට ගැළපෙන ප්‍රකාරය භාවිතයෙන් ධාවනය වනු ඇත. හොඳම ගැළපුම සඳහා, 16 KB සහාය ඇතිව යෙදුම නැවත සම්පාදනය කරන්න. වැඩිපුර තොරතුරු සඳහා, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; බලන්න"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"දත්ත <xliff:g id="CARRIERDISPLAY">%s</xliff:g> වෙත මාරු කරන ලදි"</string>
@@ -1059,7 +1062,7 @@
<string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"<xliff:g id="CELL_INDEX">%1$s</xliff:g> කොටුව එකතු කරන ලදි"</string>
<string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"රටාව සම්පූර්ණයි"</string>
<string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"රටා ප්‍රදේශය."</string>
- <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%%1$s. %%3$d න් %%2$d විජටය."</string>
+ <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. %3$d න් %2$d විජටය."</string>
<string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"විජටය එක් කරන්න."</string>
<string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"හිස්"</string>
<string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"අගුළු අරින ප්‍රදේශය විදහා ඇත."</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"පැ<xliff:g id="COUNT">%d</xliff:g>කින්"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"දි<xliff:g id="COUNT">%d</xliff:g>කින්"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"ව<xliff:g id="COUNT">%d</xliff:g>කින්"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{මිනිත්තු #කට පෙර}one{මිනිත්තු #කට පෙර}other{මිනිත්තු #කට පෙර}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{පැය #කට පෙර}one{පැය #කට පෙර}other{පැය #කට පෙර}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{දින #කට පෙර}one{දින #කට පෙර}other{දින #කට පෙර}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="END">%2$s</xliff:g> සිට <xliff:g id="START">%1$s</xliff:g> දක්වා"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ඕනෑම දින දර්ශනයක්"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> සමහර ශබ්ද නිහඬ කරමින්"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"ඔබේ උපාංගය සමගින් ගැටලුවක් ඇති අතර, ඔබේ කර්මාන්තශාලා දත්ත යළි සකසන තෙක් එය අස්ථායි විය හැකිය."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index d211d8c481f7..3277781640f6 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"V predvolenom nastavení je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Obmedzené"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Táto aplikácia nie je kompatibilná s režimom 16 kB. Nepodarilo sa skontrolovať zarovnanie súboru APK. Táto aplikácia bude spustená v režime kompatibilnom s veľkosťou stránky. Ak chcete dosiahnuť najlepšiu kompatibilitu, znova skompilujte aplikáciu s podporou 16 kB. Viac sa dozviete na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Táto aplikácia nie je kompatibilná s režimom 16 kB. Nepodarilo sa skontrolovať zarovnanie formátu ELF. Táto aplikácia bude spustená v režime kompatibilnom s veľkosťou stránky. Ak chcete dosiahnuť najlepšiu kompatibilitu, znova skompilujte aplikáciu s podporou 16 kB. Viac sa dozviete na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Táto aplikácia nie je kompatibilná s režimom 16 kB. Nepodarilo sa skontrolovať zarovnanie súboru APK a formátu ELF. Táto aplikácia bude spustená v režime kompatibilnom s veľkosťou stránky. Ak chcete dosiahnuť najlepšiu kompatibilitu, znova skompilujte aplikáciu s podporou 16 kB. Viac sa dozviete na &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba nie je poskytovaná."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nemôžete meniť nastavenie identifikácie volajúcich."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Dátové pripojenie bolo prepnuté na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"o <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"o <xliff:g id="COUNT">%d</xliff:g> d."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"o <xliff:g id="COUNT">%d</xliff:g> r."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Pred # minútou}few{Pred # minútami}many{Pred # minúty}other{Pred # minútami}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Pred # hodinou}few{Pred # hodinami}many{Pred # hodiny}other{Pred # hodinami}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Pred # dňom}few{Pred # dňami}many{Pred # dňa}other{Pred # dňami}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Ľubovoľný kalendár"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypína niektoré zvuky"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Vo vašom zariadení došlo k internému problému. Môže byť nestabilné, kým neobnovíte jeho výrobné nastavenia."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index ca854340fc43..dc61496dfca3 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID klicatelja je ponastavljen na omejeno. Naslednji klic: ni omejeno"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: omejeno"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: ni omejeno"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanje usklajenosti oblike zapisa APK ni uspelo. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanje usklajenosti oblike zapisa ELF ni uspelo. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanja usklajenosti oblik zapisov APK in ELF niso uspela. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Storitev ni nastavljena in omogočena."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ne morete spremeniti nastavitve ID-ja klicatelja."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Prenos podatkov je preklopljen na operaterja <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"čez <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"čez <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"čez <xliff:g id="COUNT">%d</xliff:g> l"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Pred # minuto}one{Pred # minuto}two{Pred # minutama}few{Pred # minutami}other{Pred # minutami}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Pred # uro}one{Pred # uro}two{Pred # urama}few{Pred # urami}other{Pred # urami}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Pred # dnevom}one{Pred # dnevom}two{Pred # dnevoma}few{Pred # dnevi}other{Pred # dnevi}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> do <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kateri koli koledar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izklaplja nekatere zvoke"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Vaša naprava ima notranjo napako in bo morda nestabilna, dokler je ne ponastavite na tovarniške nastavitve."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 45cf31a9b0e8..44981bb37708 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të kufizuar. Telefonata e radhës: e pakufizuar!"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e kufizuar!"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e pakufizuar!"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ky aplikacion nuk është i përputhshëm me modalitetin 16 KB. Kontrolli i drejtvendosjes për APK dështoi. Ky aplikacion do të ekzekutohet duke përdorur modalitetin në përputhje me madhësinë e faqes. Për përputhshmërinë më të mirë, ripërpiloje aplikacionin me mbështetjen e modalitetit 16 KB. Për më shumë informacione, shiko &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ky aplikacion nuk është i përputhshëm me modalitetin 16 KB. Kontrolli i drejtvendosjes për ELF dështoi. Ky aplikacion do të ekzekutohet duke përdorur modalitetin në përputhje me madhësinë e faqes. Për përputhshmërinë më të mirë, ripërpiloje aplikacionin me mbështetjen e modalitetit 16 KB. Për më shumë informacione, shiko &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ky aplikacion nuk është i përputhshëm me modalitetin 16 KB. Kontrollet e drejtvendosjes për APK dhe ELF dështuan. Ky aplikacion do të ekzekutohet duke përdorur modalitetin në përputhje me madhësinë e faqes. Për përputhshmërinë më të mirë, ripërpiloje aplikacionin me mbështetjen e modalitetit 16 KB. Për më shumë informacione, shiko &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Shërbimi nuk është përgatitur."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Nuk mund ta ndryshosh cilësimin e ID-së së telefonuesit."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Të dhënat u kaluan te <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"për <xliff:g id="COUNT">%d</xliff:g> orë"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"për <xliff:g id="COUNT">%d</xliff:g> ditë"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"për <xliff:g id="COUNT">%d</xliff:g> vit"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minutë më parë}other{# minuta më parë}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# orë më parë}other{# orë më parë}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ditë më parë}other{# ditë më parë}}"</string>
@@ -1665,7 +1700,7 @@
<string name="default_audio_route_name_headphones" msgid="6954070994792640762">"Kufjet"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
<string name="default_audio_route_category_name" msgid="5241740395748134483">"Sistemi"</string>
- <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Audioja e Bluetooth-it"</string>
+ <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Audioja me Bluetooth"</string>
<string name="wireless_display_route_description" msgid="8297563323032966831">"Ekran wireless"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Transmeto"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Lidhu me pajisjen"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Çdo kalendar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> po çaktivizon disa tinguj"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Ka një problem të brendshëm me pajisjen tënde. Ajo mund të jetë e paqëndrueshme derisa të rivendosësh të dhënat në gjendje fabrike."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 5a3bae0012bc..cc2248c02229 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -72,6 +72,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ИД позиваоца је подразумевано ограничен. Следећи позив: Није ограничен."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ИД позиваоца подразумевано није ограничен. Следећи позив: ограничен."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ова апликација није компатибилна са величином странице од 16 kB. Провера усклађености APK-а није успела. Ова апликација ће радити помоћу режима компатибилног са величином странице. Да бисте обезбедили најбољу компатибилност, поново компајлирајте апликацију са подршком за величину странице од 16 kB. Више информација потражите на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ова апликација није компатибилна са величином странице од 16 kB. Провера усклађености ELF-а није успела. Ова апликација ће радити помоћу режима компатибилног са величином странице. Да бисте обезбедили најбољу компатибилност, поново компајлирајте апликацију са подршком за величину странице од 16 kB. Више информација потражите на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ова апликација није компатибилна са величином странице од 16 kB. Провере усклађености APK-а и ELF-а нису успеле. Ова апликација ће радити помоћу режима компатибилног са величином странице. Да бисте обезбедили најбољу компатибилност, поново компајлирајте апликацију са подршком за величину странице од 16 kB. Више информација потражите на &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилни подаци су пребачени на оператера <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1156,6 +1159,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"за <xliff:g id="COUNT">%d</xliff:g> с"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"за <xliff:g id="COUNT">%d</xliff:g> д"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"за <xliff:g id="COUNT">%d</xliff:g> год"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Пре # минут}one{Пре # минут}few{Пре # минута}other{Пре # минута}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Пре # сат}one{Пре # сат}few{Пре # сата}other{Пре # сати}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Пре # дан}one{Пре # дан}few{Пре # дана}other{Пре # дана}}"</string>
@@ -1948,6 +1983,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Било који календар"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> искључује неке звуке"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Дошло је до интерног проблема у вези са уређајем и можда ће бити нестабилан док не обавите ресетовање на фабричка подешавања."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index fde90235e6ee..a7207ac8156e 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nummerpresentatörens standardinställning är blockerad. Nästa samtal: Inte blockerad"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Blockerad"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Inte blockerad"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Den här appen är inte kompatibel med 16 KB. APK-justeringskontrollen misslyckades. Appen körs i kompatibilitetsläget för sidstorlek. För bästa möjliga kompatibilitet bör du kompilera appen igen med stöd för 16 kB. Läs mer på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Den här appen är inte kompatibel med 16 KB. ELF-justeringskontrollen misslyckades. Appen körs i kompatibilitetsläget för sidstorlek. För bästa möjliga kompatibilitet bör du kompilera appen igen med stöd för 16 kB. Läs mer på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Den här appen är inte kompatibel med 16 kB. APK- och ELF-justeringskontrollerna misslyckades. Appen körs i kompatibilitetsläget för sidstorlek. För bästa möjliga kompatibilitet bör du kompilera appen igen med stöd för 16 kB. Läs mer på &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjänsten är inte etablerad."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Det går inte att ändra inställningen för nummerpresentatör."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Du har bytt mobildata till <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"om <xliff:g id="COUNT">%d</xliff:g> tim"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"om <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"om <xliff:g id="COUNT">%d</xliff:g> år"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{För # minut sedan}other{För # minuter sedan}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{För # timme sedan}other{För # timmar sedan}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{För # dag sedan}other{För # dagar sedan}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> till <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alla kalendrar"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> stänger av vissa ljud"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Ett internt problem har uppstått i enheten, och det kan hända att problemet kvarstår tills du återställer standardinställningarna."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2a62c87d16d9..a7d4d863187d 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Chaguomsingi za kitambulisho cha mpigaji simu huwa kuzuiwa. Simu ifuatayo: Haijazuiliwa"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo:Imezuiliwa"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo: Haijazuiliwa"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Programu hii haioani na ukubwa wa ukurasa wa KB 16. Imeshindwa kukagua mpangilio wa APK. Programu hii itaendeshwa katika hali inayooana na ukubwa wa ukurasa. Tafadhali rekebisha mipangilio ya programu ijumuishe KB 16 ili ioane vizuri. Tembelea &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; upate maelezo zaidi"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Programu hii haioani na ukubwa wa ukurasa wa KB 16. Imeshindwa kukagua mpangilio wa ELF. Programu hii itaendeshwa katika hali inayooana na ukubwa wa ukurasa. Tafadhali rekebisha mipangilio ya programu ijumuishe KB 16 ili ioane vizuri. Tembelea &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; upate maelezo zaidi"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Programu hii haioani na ukubwa wa ukurasa wa KB 16. Imeshindwa kukagua mpangilio wa APK na ELF. Programu hii itaendeshwa katika hali inayooana na ukubwa wa ukurasa. Tafadhali rekebisha mipangilio ya programu ijumuishe KB 16 ili ioane vizuri. Tembelea &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; upate maelezo zaidi"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Huduma haitathminiwi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Hauwezi kubadilisha mpangilio wa kitambulisho cha anayepiga."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Sasa unatumia data ya mtandao wa <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"baada ya saa <xliff:g id="COUNT">%d</xliff:g>"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"baada ya siku <xliff:g id="COUNT">%d</xliff:g>"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"baada ya mwaka <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Dakika # iliyopita}other{Dakika # zilizopita}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Saa # iliyopita}other{Saa # zilizopita}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Siku # iliyopita}other{Siku # zilizopita}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hadi <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kalenda yoyote"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> inazima baadhi ya sauti"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Kuna hitilafu ya ndani ya kifaa chako, na huenda kisiwe thabiti mpaka urejeshe mipangilio ya kiwandani."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 6ff96c00ea8d..6107dbee9b26 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்பட்டது என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்பட்டது"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"இந்த ஆப்ஸ் 16 KB இணக்கத்தன்மை வாய்ந்தது அல்ல. APK சீரமைவைச் சரிபார்க்க முடியவில்லை. பக்க அளவுடனான இணக்கத்தன்மைப் பயன்முறையைப் பயன்படுத்தி இந்த ஆப்ஸ் இயக்கப்படும். சிறந்த இணக்கத்தன்மைக்கு, ஆப்ஸை 16 KB ஆதரவுடன் ரீ-கம்பைல் செய்யவும். கூடுதல் தகவல்களுக்கு, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; எனும் பக்கத்தைப் பார்க்கவும்."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"இந்த ஆப்ஸ் 16 KB இணக்கத்தன்மை வாய்ந்தது அல்ல. ELF சீரமைவைச் சரிபார்க்க முடியவில்லை. பக்க அளவுடனான இணக்கத்தன்மைப் பயன்முறையைப் பயன்படுத்தி இந்த ஆப்ஸ் இயக்கப்படும். சிறந்த இணக்கத்தன்மைக்கு, ஆப்ஸை 16 KB ஆதரவுடன் ரீ-கம்பைல் செய்யவும். கூடுதல் தகவல்களுக்கு, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; எனும் பக்கத்தைப் பார்க்கவும்."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"இந்த ஆப்ஸ் 16 KB இணக்கத்தன்மை வாய்ந்தது அல்ல. APK மற்றும் ELF சீரமைவுகளைச் சரிபார்க்க முடியவில்லை. பக்க அளவுடனான இணக்கத்தன்மைப் பயன்முறையைப் பயன்படுத்தி இந்த ஆப்ஸ் இயக்கப்படும். சிறந்த இணக்கத்தன்மைக்கு, ஆப்ஸை 16 KB ஆதரவுடன் ரீ-கம்பைல் செய்யவும். கூடுதல் தகவல்களுக்கு, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; எனும் பக்கத்தைப் பார்க்கவும்."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"சேவை ஒதுக்கப்படவில்லை."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"அழைப்பாளர் ஐடி அமைப்பை மாற்ற முடியாது."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"டேட்டா <xliff:g id="CARRIERDISPLAY">%s</xliff:g>க்கு மாற்றப்பட்டது"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> மணிநேரத்தில்"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> நாட்களில்"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> ஆண்டில்"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# நிமிடத்திற்கு முன்பு}other{# நிமிடங்களுக்கு முன்பு}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# மணிநேரத்திற்கு முன்பு}other{# மணிநேரத்திற்கு முன்பு}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# நாளுக்கு முன்பு}other{# நாட்களுக்கு முன்பு}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> முதல் <xliff:g id="END">%2$s</xliff:g> வரை"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ஏதேனும் கேலெண்டர்"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> சில ஒலிகளை முடக்குகிறது"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது, அதனை ஆரம்பநிலைக்கு மீட்டமைக்கும் வரை நிலையற்று இயங்கலாம்."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 68fce1f8175a..53ef42a784af 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి ఉంటుంది. తర్వాత కాల్: పరిమితి లేదు"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి ఉంటుంది"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ఈ యాప్ 16 KBకి అనుకూలమైనది కాదు. APK అమరిక చెకింగ్ విఫలమైంది. ఈ యాప్ పేజీ సైజ్ అనుకూల మోడ్‌ను ఉపయోగించి రన్ అవుతుంది. సరైన అనుకూలత కోసం, దయచేసి 16 KB సపోర్ట్‌తో అప్లికేషన్‌ను మళ్లీ కంపైల్ చేయండి. మరింత సమాచారం కోసం, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ను చూడండి"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ఈ యాప్ 16 KBకి అనుకూలమైనది కాదు. ELF అమరిక చెకింగ్ విఫలమైంది. ఈ యాప్ పేజీ సైజ్ అనుకూల మోడ్‌ను ఉపయోగించి రన్ అవుతుంది. సరైన అనుకూలత కోసం, దయచేసి 16 KB సపోర్ట్‌తో అప్లికేషన్‌ను మళ్లీ కంపైల్ చేయండి. మరింత సమాచారం కోసం, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ను చూడండి"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ఈ యాప్ 16 KBకి అనుకూలమైనది కాదు. APK, ELF అమరిక చెకప్ దశలు విఫలమయ్యాయి. ఈ యాప్ పేజీ సైజ్ అనుకూల మోడ్‌ను ఉపయోగించి రన్ అవుతుంది. సరైన అనుకూలత కోసం, దయచేసి 16 KB సపోర్ట్‌తో అప్లికేషన్‌ను మళ్లీ కంపైల్ చేయండి. మరింత సమాచారం కోసం, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ను చూడండి"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్‌ను మార్చలేరు."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"డేటాను <xliff:g id="CARRIERDISPLAY">%s</xliff:g>కు స్విచ్ చేశారు"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>గంటలో"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>రోజులో"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>సంవత్సరంలో"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# నిమిషం క్రితం}other{# నిమిషాల క్రితం}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# గంట క్రితం}other{# గంటల క్రితం}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# రోజు క్రితం}other{# రోజుల క్రితం}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> నుండి <xliff:g id="END">%2$s</xliff:g> వరకు"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ఏదైనా క్యాలెండర్"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> కొన్ని ధ్వనులను మ్యూట్ చేస్తోంది"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది మరియు మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసే వరకు అస్థిరంగా ఉంటుంది."</string>
@@ -2442,7 +2478,7 @@
<string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
<string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"ఈ దేశంలో లేదా ప్రాంతంలో ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
<string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"ఎమర్జెన్సీ శాటిలైట్ సహాయం సెటప్ చేయలేదు"</string>
- <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ సెట్టింగ్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
+ <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
<string name="satellite_sos_location_disabled_notification_title" msgid="5427987916850950591">"ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
<string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"ఈ దేశంలో లేదా ప్రాంతంలో ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో ఉందో లేదో చెక్ చేయడానికి, లొకేషన్ సెట్టింగ్‌లను ఆన్ చేయండి"</string>
<string name="satellite_messaging_available_notification_title" msgid="3366657987618784706">"శాటిలైట్ మెసేజింగ్ అందుబాటులో ఉంది"</string>
@@ -2454,7 +2490,7 @@
<string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
<string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"ఈ దేశంలో లేదా ప్రాంతంలో శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
<string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"శాటిలైట్ మెసేజింగ్‌ను సెటప్ చేయలేదు"</string>
- <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ సెట్టింగ్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
<string name="satellite_messaging_location_disabled_notification_title" msgid="7270641894250928494">"శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
<string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"ఈ దేశంలో లేదా ప్రాంతంలో శాటిలైట్ మెసేజింగ్ అందుబాటులో ఉందో లేదో చెక్ చేయడానికి, లొకేషన్ సెట్టింగ్‌లను ఆన్ చేయండి"</string>
<string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"వేలిముద్ర అన్‌లాక్‌ను మళ్లీ సెటప్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 178a76d3a404..43b142357998 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นถูกจำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ถูกจำกัด"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"แอปนี้ไม่รองรับขนาดหน้า 16 KB ผลการตรวจสอบการจัดตำแหน่ง APK คือไม่ผ่าน ด้วยเหตุนี้ แอปจะทำงานโดยใช้โหมดการใช้งานร่วมกับขนาดหน้าได้ เพื่อให้ใช้งานร่วมกันได้อย่างดีที่สุด โปรดคอมไพล์แอปพลิเคชันอีกครั้งโดยมีการรองรับขนาดหน้า 16 KB ดูข้อมูลเพิ่มเติมได้ที่ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"แอปนี้ไม่รองรับขนาดหน้า 16 KB ผลการตรวจสอบการจัดตำแหน่ง ELF คือไม่ผ่าน ด้วยเหตุนี้ แอปจะทำงานโดยใช้โหมดการใช้งานร่วมกับขนาดหน้าได้ เพื่อให้ใช้งานร่วมกันได้อย่างดีที่สุด โปรดคอมไพล์แอปพลิเคชันอีกครั้งโดยมีการรองรับขนาดหน้า 16 KB ดูข้อมูลเพิ่มเติมได้ที่ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"แอปนี้ไม่รองรับขนาดหน้า 16 KB ผลการตรวจสอบการจัดตำแหน่งของ APK และ ELF คือไม่ผ่าน ด้วยเหตุนี้ แอปจะทำงานโดยใช้โหมดการใช้งานร่วมกับขนาดหน้าได้ เพื่อให้ใช้งานร่วมกันได้อย่างดีที่สุด โปรดคอมไพล์แอปพลิเคชันอีกครั้งโดยมีการรองรับขนาดหน้า 16 KB ดูข้อมูลเพิ่มเติมได้ที่ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"ไม่มีการนำเสนอบริการ"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"คุณไม่สามารถเปลี่ยนการตั้งค่าหมายเลขผู้โทร"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"เปลี่ยนไปใช้อินเทอร์เน็ตมือถือของ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> แล้ว"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"ใน <xliff:g id="COUNT">%d</xliff:g> ชม."</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"ใน <xliff:g id="COUNT">%d</xliff:g> วัน"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"ใน <xliff:g id="COUNT">%d</xliff:g> ปี"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# นาทีที่ผ่านมา}other{# นาทีที่ผ่านมา}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# ชั่วโมงที่ผ่านมา}other{# ชั่วโมงที่ผ่านมา}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# วันที่ผ่านมา}other{# วันที่ผ่านมา}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ถึง<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ปฏิทินทั้งหมด"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> กำลังปิดเสียงบางรายการ"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง อุปกรณ์อาจทำงานไม่เสถียรจนกว่าคุณจะรีเซ็ตข้อมูลเป็นค่าเริ่มต้น"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 8e3c5e7433a7..1dbd00bed90a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nade-default sa pinaghihigpitan ang Caller ID. Susunod na tawag: hindi pinaghihigpitan"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Naka-default sa hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Pinaghihigpitan"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Naka-default na hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Hindi pinaghihigpitan"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Hindi 16 KB compatible ang app na ito. Hindi pumasa ang alignment check sa APK. Patatakbuhin ang app na ito gamit ang page size compatible mode. Para sa pinakamahusay na compatibility, paki-recompile ang application nang may suporta sa 16 KB. Para sa higit pang impormasyon, tingnan ang &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Hindi 16 KB compatible ang app na ito. Hindi pumasa ang alignment check sa ELF. Patatakbuhin ang app na ito gamit ang page size compatible mode. Para sa pinakamahusay na compatibility, paki-recompile ang application nang may suporta sa 16 KB. Para sa higit pang impormasyon, tingnan ang &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Hindi 16 KB compatible ang app na ito. Hindi pumasa ang mga alignment check sa APK at ELF. Patatakbuhin ang app na ito gamit ang page size compatible mode. Para sa pinakamahusay na compatibility, paki-recompile ang application nang may suporta sa 16 KB. Para sa higit pang impormasyon, tingnan ang &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Hindi naprobisyon ang serbisyo."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Hindi mo mababago ang setting ng caller ID."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Nailipat sa <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ang data"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"sa <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"sa <xliff:g id="COUNT">%d</xliff:g>d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"sa <xliff:g id="COUNT">%d</xliff:g>y"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minuto ang nakalipas}one{# minuto ang nakalipas}other{# na minuto ang nakalipas}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# oras ang nakalipas}one{# oras ang nakalipas}other{# na oras ang nakalipas}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# araw ang nakalipas}one{# araw ang nakalipas}other{# na araw ang nakalipas}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> patungong <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Anumang kalendaryo"</string>
<string name="muted_by" msgid="91464083490094950">"Minu-mute ng <xliff:g id="THIRD_PARTY">%1$s</xliff:g> ang ilang tunog"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"May internal na problema sa iyong device, at maaaring hindi ito maging stable hanggang sa i-reset mo ang factory data."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 98b1dd3ef245..3777f04ca136 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Arayan kimliği varsayılanları kısıtlanmıştır. Sonraki çağrı: Kısıtlanmamış"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmış"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmamış"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Bu uygulama 16 KB ile uyumlu değil. APK uyumluluk kontrolü başarısız oldu. Bu uygulama, sayfa boyutuna uyumlu mod kullanılarak çalıştırılacak. En iyi uyumluluk için lütfen uygulamayı 16 KB desteğiyle yeniden derleyin. Daha fazla bilgi için &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; adresini ziyaret edin."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Bu uygulama 16 KB ile uyumlu değil. ELF uyumluluk kontrolü başarısız oldu. Bu uygulama, sayfa boyutuna uyumlu mod kullanılarak çalıştırılacak. En iyi uyumluluk için lütfen uygulamayı 16 KB desteğiyle yeniden derleyin. Daha fazla bilgi için &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; adresini ziyaret edin."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Bu uygulama 16 KB ile uyumlu değil. APK ve ELF uyumluluk kontrolleri başarısız oldu. Bu uygulama, sayfa boyutuna uyumlu mod kullanılarak çalıştırılacak. En iyi uyumluluk için lütfen uygulamayı 16 KB desteğiyle yeniden derleyin. Daha fazla bilgi için &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; adresini ziyaret edin."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Hizmet sağlanamadı."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Arayanın kimliği ayarını değiştiremezsiniz."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Veriler şuraya aktarıldı: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> saat içinde"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> gün içinde"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> yıl içinde"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# dakika önce}other{# dakika önce}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# saat önce}other{# saat önce}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# gün önce}other{# gün önce}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Tüm takvimler"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bazı sesleri kapatıyor"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızla ilgili dahili bir sorun oluştu ve fabrika verilerine sıfırlama işlemi gerçekleştirilene kadar kararsız çalışabilir."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d5e6bd81ffcc..3d603e34c395 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -73,6 +73,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ідентиф. абонента за умовч. обмеж. Наст. дзвінок: не обмеж."</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: обмеж."</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: не обмежений"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Цей додаток несумісний зі сторінками пам’яті з розміром 16 КБ. Не вдалося виконати перевірку вирівнювання APK. Цей додаток працюватиме в режимі сумісності з розміром сторінки. Для оптимальної сумісності перекомпілюйте додаток так, щоб він підтримував сторінки пам’яті з розміром 16 КБ. Щоб дізнатися більше, перегляньте статтю &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Цей додаток несумісний зі сторінками пам’яті з розміром 16 КБ. Не вдалося виконати перевірку вирівнювання ELF. Цей додаток працюватиме в режимі сумісності з розміром сторінки. Для оптимальної сумісності перекомпілюйте додаток так, щоб він підтримував сторінки пам’яті з розміром 16 КБ. Щоб дізнатися більше, перегляньте статтю &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Цей додаток несумісний зі сторінками пам’яті з розміром 16 КБ. Не вдалося виконати перевірку вирівнювання APK і ELF. Цей додаток працюватиме в режимі сумісності з розміром сторінки. Для оптимальної сумісності перекомпілюйте додаток так, щоб він підтримував сторінки пам’яті з розміром 16 КБ. Щоб дізнатися більше, перегляньте статтю &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Службу не ініціалізовано."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ви не можете змінювати налаштування ідентифікатора абонента."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Мобільний Інтернет переключено на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1157,6 +1160,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"через <xliff:g id="COUNT">%d</xliff:g> год"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"через <xliff:g id="COUNT">%d</xliff:g> дн."</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"через <xliff:g id="COUNT">%d</xliff:g> р."</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# хвилину тому}one{# хвилину тому}few{# хвилини тому}many{# хвилин тому}other{# хвилини тому}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# годину тому}one{# годину тому}few{# години тому}many{# годин тому}other{# години тому}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# день тому}one{# день тому}few{# дні тому}many{# днів тому}other{# дня тому}}"</string>
@@ -1949,6 +1984,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"З усіх календарів"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> вимикає деякі звуки"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Через внутрішню помилку ваш пристрій може працювати нестабільно. Відновіть заводські налаштування."</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index b26b1c102abc..8a9c661658a3 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"‏کالر ID کی ڈیفالٹ ترتیب محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: محدود کردہ"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"‏یہ ایپ ‎16 KB کے موافق نہیں ہے۔ ‫APK الائنمنٹ چیک ناکام ہو گیا۔ یہ ایپ صفحہ کے سائز کے ساتھ موافقت رکھنے والے موڈ کے ساتھ چلے گی۔ بہترین موافقت کے لیے، براہ کرم ‎16 KB کے سپورٹ کے ساتھ ایپلیکیشن کو دوبارہ مرتب کریں۔ مزید معلومات کے لیے، ‎&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;‎ دیکھیں"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"‏یہ ایپ ‎16 KB کے موافق نہیں ہے۔ ‫ELF الائنمنٹ چیک ناکام ہو گیا۔ یہ ایپ صفحہ کے سائز کے ساتھ موافقت رکھنے والے موڈ کے ساتھ چلے گی۔ بہترین موافقت کے لیے، براہ کرم ‎16 KB کے سپورٹ کے ساتھ ایپلیکیشن کو دوبارہ مرتب کریں۔ مزید معلومات کے لیے، ‎&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;‎ دیکھیں"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"‏یہ ایپ ‎16 KB کے موافق نہیں ہے۔ ‫APK اور ELF الائنمنٹ چیکس ناکام ہو گئے۔ یہ ایپ صفحہ کے سائز کے ساتھ موافقت رکھنے والے موڈ کے ساتھ چلے گی۔ بہترین موافقت کے لیے، براہ کرم ‎16 KB کے سپورٹ کے ساتھ ایپلیکیشن کو دوبارہ مرتب کریں۔ مزید معلومات کے لیے، ‎&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;‎ دیکھیں"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"‏آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"ڈیٹا <xliff:g id="CARRIERDISPLAY">%s</xliff:g> پر سوئچ کیا گیا"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> گھنٹے میں"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> دن میں"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> سال میں"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# منٹ پہلے}other{# منٹ پہلے}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# گھنٹہ پہلے}other{# گھنٹے پہلے}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# دن پہلے}other{# دن پہلے}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> تا <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"کوئی بھی کیلنڈر"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> کچھ آوازوں کو خاموش کر رہا ہے"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"آپ کے آلہ میں ایک داخلی مسئلہ ہے اور جب تک آپ فیکٹری ڈیٹا کو دوبارہ ترتیب نہیں دے دیتے ہیں، ہوسکتا ہے کہ یہ غیر مستحکم رہے۔"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 97e044887d7e..28f06ab64d15 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklangan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklangan"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Bu ilova 16 KB sahifa hajmi bilan mos emas. APK fayllardagi maʼlumotlarning tekislanishi tekshirilmadi. Bu ilova sahifa hajmiga mos rejimda ishlaydi. Eng yaxshi moslik uchun ilovani 16 KB sahifa hajmi bilan qayta kompilyatsiya qiling. Batafsil: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Bu ilova 16 KB sahifa hajmi bilan mos emas. ELF fayllardagi maʼlumotlarning tekislanishi tekshirilmadi. Bu ilova sahifa hajmiga mos rejimda ishlaydi. Eng yaxshi moslik uchun ilovani 16 KB sahifa hajmi bilan qayta kompilyatsiya qiling. Batafsil: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Bu ilova 16 KB sahifa hajmi bilan mos emas. APK va ELF fayllardagi maʼlumotlarning tekislanishi tekshirilmadi. Bu ilova sahifa hajmiga mos rejimda ishlaydi. Eng yaxshi moslik uchun ilovani 16 KB sahifa hajmi bilan qayta kompilyatsiya qiling. Batafsil: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Xizmat ishalamaydi."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Qo‘ng‘iroq qiluvchining ID raqami sozlamasini o‘zgartirib bo‘lmaydi."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Internet <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoriga almashtirildi"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> soatdan keyin"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> kundan keyin"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> yildan keyin"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# daqiqa oldin}other{# daqiqa oldin}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# soat oldin}other{# soat oldin}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# kun oldin}other{# kun oldin}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Har qanday taqvim"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ayrim tovushlarni ovozsiz qilgan"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. U zavod sozlamalari tiklanmaguncha barqaror ishlamasligi mumkin."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1ebff32f7d20..f42e46ffb6cc 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Số gọi đến mặc định thành bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Bị giới hạn"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ứng dụng này không tương thích với kích thước trang 16 KB. Không kiểm tra được sự tương thích của tệp APK. Ứng dụng này sẽ chạy ở chế độ tương thích với kích thước trang. Để đảm bảo khả năng tương thích tốt nhất, vui lòng biên dịch lại ứng dụng ở chế độ hỗ trợ kích thước trang 16 KB. Để biết thêm thông tin, hãy xem &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ứng dụng này không tương thích với kích thước trang 16 KB. Không kiểm tra được sự tương thích của tệp ELF. Ứng dụng này sẽ chạy ở chế độ tương thích với kích thước trang. Để đảm bảo khả năng tương thích tốt nhất, vui lòng biên dịch lại ứng dụng ở chế độ hỗ trợ kích thước trang 16 KB. Để biết thêm thông tin, hãy xem &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ứng dụng này không tương thích với kích thước trang 16 KB. Không kiểm tra được sự tương thích của tệp APK và tệp ELF. Ứng dụng này sẽ chạy ở chế độ tương thích với kích thước trang. Để đảm bảo khả năng tương thích tốt nhất, vui lòng biên dịch lại ứng dụng ở chế độ hỗ trợ kích thước trang 16 KB. Để biết thêm thông tin, hãy xem &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Dịch vụ không được cấp phép."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Bạn không thể thay đổi cài đặt ID người gọi."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Đã chuyển dữ liệu sang <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> giờ nữa"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> ngày nữa"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> năm nữa"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# phút trước}other{# phút trước}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# giờ trước}other{# giờ trước}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# ngày trước}other{# ngày trước}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> đến <xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bất kỳ lịch nào"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> đang tắt một số âm thanh"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Đã xảy ra sự cố nội bộ với thiết bị của bạn và thiết bị có thể sẽ không ổn định cho tới khi bạn thiết lập lại dữ liệu ban đầu."</string>
diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml
index ffa3b9c614db..7232786d92c9 100644
--- a/core/res/res/values-watch-v36/dimens_material.xml
+++ b/core/res/res/values-watch-v36/dimens_material.xml
@@ -31,4 +31,9 @@
<!-- Opacity factor for disabled material3 widget -->
<dimen name="disabled_alpha_device_default">0.12</dimen>
<dimen name="primary_content_alpha_device_default">0.38</dimen>
+
+ <!-- values for material3 progress bar(progress indicator) -->
+ <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2.12</item>
+ <dimen name="progressbar_thickness">8dp</dimen>
+ <dimen name="progressbar_elevation">0.1dp</dimen>
</resources>
diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml
index 7da7435930b1..6e5ef68a70c3 100644
--- a/core/res/res/values-watch-v36/styles_material.xml
+++ b/core/res/res/values-watch-v36/styles_material.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2024 The Android Open Source Project
~
@@ -17,13 +17,13 @@
<resources>
<!-- Button Styles -->
- <!-- Material Button - Filled -->
+ <!-- Material Button - Filled (primary colored) -->
<style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button.WearMaterial3">
<item name="android:background">@drawable/btn_background_material_filled</item>
<item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item>
</style>
- <!-- Material Button - Filled Tonal(Override system default button styles) -->
+ <!-- Material Button - Filled Tonal (Override system default button styles) -->
<style name="Widget.DeviceDefault.Button.WearMaterial3">
<item name="background">@drawable/btn_background_material_filled_tonal</item>
<item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item>
@@ -41,9 +41,19 @@
<item name="gravity">center_vertical</item>
</style>
+ <!-- Material Button - Outlined -->
+ <style name="Widget.DeviceDefault.Button.Outlined" parent="Widget.DeviceDefault.Button.WearMaterial3">
+ <item name="android:background">@drawable/btn_background_material_outlined</item>
+ </style>
+
+ <!-- Material Button - Text -->
+ <style name="Widget.DeviceDefault.Button.Text" parent="Widget.DeviceDefault.Button.WearMaterial3">
+ <item name="android:background">@drawable/btn_background_material_text</item>
+ </style>
+
<!-- Text Styles -->
<!-- TextAppearance for Material Button - Filled -->
- <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material">
+ <style name="TextAppearance.Widget.Button.Material.Filled">
<item name="textColor">@color/btn_material_filled_content_color</item>
</style>
@@ -57,6 +67,10 @@
</style>
<!-- AlertDialog Styles -->
+ <style name="AlertDialog.DeviceDefault.WearMaterial3">
+ <item name="layout">@layout/alert_dialog_wear_material3</item>
+ </style>
+
<style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3" parent="Widget.DeviceDefault.Button">
<item name="android:textSize">0sp</item>
<item name="android:gravity">center</item>
@@ -79,4 +93,13 @@
<item name="maxWidth">@dimen/dialog_btn_negative_width</item>
<item name="maxHeight">@dimen/dialog_btn_negative_height</item>
</style>
+
+ <!-- Wear Material3 Progress Bar style: progressed ring.-->
+ <style name="Widget.DeviceDefault.ProgressBar.WearMaterial3">
+ <item name="indeterminateOnly">false</item>
+ <item name="progressDrawable">@drawable/progress_ring_wear_material3</item>
+ <item name="minHeight">@dimen/progress_bar_height</item>
+ <item name="maxHeight">@dimen/progress_bar_height</item>
+ <item name="mirrorForRtl">true</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 529f18b78e4d..73a7c094ac3f 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -33,4 +33,50 @@
<!-- Style the scrollbars accoridngly. -->
<drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
<drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
+
+ <!--
+ Material motion physics configs
+ values from https://carbon.googleplex.com/wear-m3/pages/motion/tokens-and-specs/40358758-8b8c-4d46-9391-a8fff2d91197#15087d76-8a5a-4d52-a210-efc2cd479a66
+ -->
+ <!-- standard -->
+ <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+ <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastEffectStiffness">1400</integer>
+
+ <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultSpatialStiffness">500</integer>
+ <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultEffectStiffness">500</integer>
+
+ <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowSpatialStiffness">260</integer>
+ <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowEffectStiffness">260</integer>
+
+ <!-- expressive -->
+ <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.7</item>
+ <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+ <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveFastEffectStiffness">1400</integer>
+
+ <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.75</item>
+ <integer name="config_motionExpressiveDefaultSpatialStiffness">350</integer>
+ <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveDefaultEffectStiffness">500</integer>
+
+ <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+ <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/wear-m3/tokens/designSystems/70fbaa4f7722a3d1/tokenSets/4fa2518eaeaf65eb
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">18dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">26dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">36dp</dimen>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 64c2f70be73e..14e85574f413 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"默认不显示本机号码,但在下一次通话中显示"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"默认显示本机号码,但在下一次通话中不显示"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"默认显示本机号码,在下一次通话中也显示"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"此应用不兼容 16 KB。APK 对齐检查失败。此应用将使用页面大小兼容模式运行。为获得最佳兼容性,请将此应用重新编译为支持 16 KB。如需了解详情,请参阅 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"此应用不兼容 16 KB。ELF 对齐检查失败。此应用将使用页面大小兼容模式运行。为获得最佳兼容性,请将此应用重新编译为支持 16 KB。如需了解详情,请参阅 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"此应用不兼容 16 KB。APK 和 ELF 对齐检查失败。此应用将使用页面大小兼容模式运行。为获得最佳兼容性,请将此应用重新编译为支持 16 KB。如需了解详情,请参阅 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供服务。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"您无法更改来电显示设置。"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"移动数据网络已切换至“<xliff:g id="CARRIERDISPLAY">%s</xliff:g>”"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g>小时后"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g>天后"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g>年后"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# 分钟前}other{# 分钟前}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# 小时前}other{# 小时前}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# 天前}other{# 天前}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>到<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g><xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"所有日历"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正在将某些音效设为静音"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"您的设备内部出现了问题。如果不将设备恢复出厂设置,设备运行可能会不稳定。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 53c5d155cd27..c4079e00605b 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"預設不顯示來電號碼,但下一通電話則顯示。"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"預設顯示來電號碼,但下一通電話不顯示。"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示來電號碼,下一通電話也繼續顯示。"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"此應用程式不兼容 16KB。未能通過 APK 一致性檢查。此應用程式將使用頁面大小兼容模式運行。為達至最佳的兼容性,請將應用程式重新編譯為支援 16KB。詳情請瀏覽 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"此應用程式不兼容 16KB。未能通過電子商務記錄格式一致性檢查。此應用程式將使用頁面大小兼容模式運行。為達至最佳的兼容性,請將應用程式重新編譯為支援 16KB。詳情請瀏覽 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"此應用程式不兼容 16KB。未能通過 APK 和電子商務記錄格式一致性檢查。此應用程式將使用頁面大小兼容模式運行。為達至最佳的兼容性,請將應用程式重新編譯為支援 16KB。詳情請瀏覽 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供此服務。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"你無法更改來電顯示設定。"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"流動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> 小時後"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> 天後"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> 年後"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# 分鐘前}other{# 分鐘前}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# 小時前}other{# 小時前}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# 天前}other{# 天前}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>至<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>,<xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"任何日曆"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正將某些音效設為靜音"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"你裝置的系統發生問題,回復原廠設定後即可解決該問題。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index cbd6bd2fb48f..3c3a0ef23b84 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"預設不顯示本機號碼,但下一通電話顯示。"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"預設顯示本機號碼,但下一通電話不顯示。"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示本機號碼,下一通電話也繼續顯示。"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"這個應用程式不支援 16 KB,APK 校驗檢查失敗。這個應用程式將以頁面大小相容性模式執行。為獲得最佳相容性,請重新編譯,讓應用程式支援 16 KB。詳情請參閱 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"這個應用程式不支援 16 KB,ELF 校驗檢查失敗。這個應用程式將以頁面大小相容性模式執行。為獲得最佳相容性,請重新編譯,讓應用程式支援 16 KB。詳情請參閱 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"這個應用程式不支援 16 KB,APK 和 ELF 校驗檢查失敗。這個應用程式將以頁面大小相容性模式執行。為獲得最佳相容性,請重新編譯,讓應用程式支援 16 KB。詳情請參閱 &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"無法提供此服務。"</string>
<string name="CLIRPermanent" msgid="166443681876381118">"你無法變更來電顯示設定。"</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"行動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"<xliff:g id="COUNT">%d</xliff:g> 小時後"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"<xliff:g id="COUNT">%d</xliff:g> 天後"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"<xliff:g id="COUNT">%d</xliff:g> 年後"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# 分鐘前}other{# 分鐘前}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# 小時前}other{# 小時前}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# 天前}other{# 天前}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>到<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>、<xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"任何日曆"</string>
<string name="muted_by" msgid="91464083490094950">"「<xliff:g id="THIRD_PARTY">%1$s</xliff:g>」正在關閉部分音效"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"你的裝置發生內部問題,必須將裝置恢復原廠設定才能解除不穩定狀態。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index f498fe24267c..dc72cb7b9760 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -71,6 +71,9 @@
<string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"i-ID yomshayeli ishintshela kokuvinjiwe. Ucingo olulandelayo: Aluvinjelwe"</string>
<string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Luvinjelwe"</string>
<string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Aluvinjelwe"</string>
+ <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Le app ayihambelani ne-16 KB. Ukuhlolwa kokuqondanisa i-APK kuhlulekile. Le app izoqaliswa kusetshenziswa imodi yokuhambelana kobukhulu bekhasi. Ukuze kube nokuhambelana okuhle kakhulu, sicela uhlanganise kabusha i-app ngosekelo lwe-16 KB. Ukuze uthole imininingwane eyengeziwe, bheka &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Le app ayihambelani ne-16 KB. Ukuhlolwa kokuqondanisa i-ELF kuhlulekile. Le app izoqaliswa kusetshenziswa imodi yokuhambelana kobukhulu bekhasi. Ukuze kube nokuhambelana okuhle kakhulu, sicela uhlanganise kabusha i-app ngosekelo lwe-16 KB. Ukuze uthole imininingwane eyengeziwe, bheka &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+ <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Le app ayihambelani ne-16 KB. Ukuhlola ukuqondanisa i-APK ne-ELF kuhlulekile. Le app izoqaliswa kusetshenziswa imodi yokuhambelana kobukhulu bekhasi. Ukuze kube nokuhambelana okuhle kakhulu, sicela uhlanganise kabusha i-app ngosekelo lwe-16 KB. Ukuze uthole imininingwane eyengeziwe, bheka &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
<string name="serviceNotProvisioned" msgid="8289333510236766193">"Isevisi ayilungiselelwe."</string>
<string name="CLIRPermanent" msgid="166443681876381118">"Ngeke ukwazi ukuguqul izilungiselelo zemininingwane yoshayayo."</string>
<string name="auto_data_switch_title" msgid="3286350716870518297">"Ushintshele idatha ku-<xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1155,6 +1158,38 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"ngehora elingu-<xliff:g id="COUNT">%d</xliff:g>"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"ngosuku olu-<xliff:g id="COUNT">%d</xliff:g>"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"ngonyaka ongu-<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <!-- no translation found for duration_minutes_shortest_past (1740022450020492407) -->
+ <skip />
+ <!-- no translation found for duration_hours_shortest_past (2098397414186628489) -->
+ <skip />
+ <!-- no translation found for duration_days_shortest_past (1832006037955897625) -->
+ <skip />
+ <!-- no translation found for duration_years_shortest_past (6168256514200469291) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium (5891933490342643944) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium (1465359726485910115) -->
+ <skip />
+ <!-- no translation found for duration_days_medium (5994225628248661388) -->
+ <skip />
+ <!-- no translation found for duration_years_medium (734023884353592526) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_future (2750894988731934402) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_future (6050833881463849764) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_future (1700821545602729963) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_future (3281018940397120166) -->
+ <skip />
+ <!-- no translation found for duration_minutes_medium_past (7400424340181947714) -->
+ <skip />
+ <!-- no translation found for duration_hours_medium_past (6709441336035202617) -->
+ <skip />
+ <!-- no translation found for duration_days_medium_past (5748156261134344532) -->
+ <skip />
+ <!-- no translation found for duration_years_medium_past (893797065424596243) -->
+ <skip />
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{umzuzu odlule #}one{imizuzu edlule #}other{imizuzu edlule #}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{ihora elingu-# eledlule}one{amahora adlule angu-#}other{amahora adlule angu-#}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{usuku oludlule #}one{izinsuku ezedlule #}other{izinsuku ezedlule #}}"</string>
@@ -1947,6 +1982,7 @@
<string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
<string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"U-<xliff:g id="START">%1$s</xliff:g> ukuya ku-<xliff:g id="END">%2$s</xliff:g>"</string>
+ <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
<string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Noma iyiphi ikhalenda"</string>
<string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ithulisa eminye imisindo"</string>
<string name="system_error_wipe_data" msgid="5910572292172208493">"Kukhona inkinga yangaphakathi ngedivayisi yakho, futhi ingase ibe engazinzile kuze kube yilapho usetha kabusha yonke idatha."</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8c46ccc84f39..238aca556003 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10570,6 +10570,32 @@
<declare-styleable name="DateTimeView">
<attr name="showRelative" format="boolean" />
+ <!-- For relative times, controls what kinds of times get disambiguation text.
+
+ The default value is "future".
+
+ Does nothing if showRelative=false.
+ -->
+ <attr name="relativeTimeDisambiguationText">
+ <!-- Times in the past will have extra clarifying text indicating that the time is in
+ the past. For example, 1 minute ago is represented as "1m ago". If this flag is not
+ set, times in the past are represented as just "1m". -->
+ <flag name="past" value="0x01" />
+ <!-- Times in the future will have extra clarifying text indicating that the time is in
+ the future. For example, 1 minute in the future is represented as "in 1m". If this
+ flag is not set, times in the future are represented as just "1m". -->
+ <flag name="future" value="0x02" />
+ </attr>
+ <!-- For relative times, sets the length of the time unit displayed (minutes, hours, etc.).
+
+ Does nothing if showRelative=false.
+ -->
+ <attr name="relativeTimeUnitDisplayLength">
+ <!-- The time unit will be shown as a short as possible (1 character if possible). -->
+ <enum name="shortest" value="0" />
+ <!-- The time unit will be shortened to a medium length (2-3 characters in general). -->
+ <enum name="medium" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="ResolverDrawerLayout_LayoutParams">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index cdf184c9c944..00c59c6c0edc 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2572,6 +2572,8 @@
against a development branch, in which case it will only work against
the development builds. -->
<attr name="minSdkVersion" format="integer|string" />
+ <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+ <attr name="minSdkVersionFull" format="string" />
<!-- This is the SDK version number that the application is targeting.
It is able to run on older versions (down to minSdkVersion), but
was explicitly tested to work with the version specified here.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 73d696ba3567..53b47622e8ae 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -815,6 +815,11 @@
we rely on gravity to determine the effective orientation. -->
<bool name="config_deskDockEnablesAccelerometer">true</bool>
+ <!-- Control for allowing the dock rotation functionality before provision like
+ when the SetupWizard is being shown to the user. This defaults to false to
+ preserve existing behavior. -->
+ <bool name="config_allowDockBeforeProvision">false</bool>
+
<!-- Car dock behavior -->
<!-- The number of degrees to rotate the display when the device is in a car dock.
@@ -2174,6 +2179,16 @@
<item>com.android.location.fused</item>
</string-array>
+ <!-- Package name providing population density location support. -->
+ <string name="config_populationDensityProviderPackageName" translatable="false">com.android.location.populationdensity</string>
+
+ <!-- Whether to enable population density provider overlay, which allows the population density provider to
+ be replaced by an app at run-time. When disabled, only the
+ config_populationDensityProviderPackageName package will be searched for a population density
+ provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+ mechanism can set it to false. -->
+ <bool name="config_enablePopulationDensityProviderOverlay" translatable="false">true</bool>
+
<!-- Package name of the extension software fallback. -->
<string name="config_extensionFallbackPackageName" translatable="false"></string>
@@ -2357,7 +2372,7 @@
<string name="default_sms_application" translatable="false">com.android.messaging</string>
<!-- Flag indicating whether the current device supports "Ask every time" for sms-->
- <bool name="config_sms_ask_every_time_support">true</bool>
+ <bool name="config_sms_ask_every_time_support">false</bool>
<!-- Flag indicating whether the current device allows acknowledgement of SIM operation like
SM-PP or saving SMS to SIM can be done via the IMS interfaces.
@@ -2451,6 +2466,9 @@
<string name="config_systemCallStreaming" translatable="false"></string>
<!-- The name of the package that will hold the default retail demo role. -->
<string name="config_defaultRetailDemo" translatable="false"></string>
+ <!-- The name of the package that will hold the default reserved for testing profile group
+ exclusivity role. -->
+ <string name="config_defaultReservedForTestingProfileGroupExclusivity" translatable="false">android.app.rolemultiuser.cts.app</string>
<!-- The component name of the wear service class that will be started by the system server. -->
<string name="config_wearServiceComponent" translatable="false"></string>
@@ -2779,6 +2797,9 @@
If empty, logs "other" for all. -->
<string-array name="config_loggable_dream_prefixes"></string-array>
+ <!-- Whether to enable glanceable hub features on this device. -->
+ <bool name="config_glanceableHubEnabled">false</bool>
+
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
try to start this dream if possible. The dream should typically call startDozing()
@@ -4207,13 +4228,12 @@
must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
<string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
- <!-- Allow the gesture to double tap the power button to trigger a target action. -->
- <bool name="config_doubleTapPowerGestureEnabled">true</bool>
<!-- Allow the gesture to double tap the power button twice to start the camera while the device
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
- <!-- Allow the gesture to double tap the power button twice to launch the wallet. -->
- <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool>
+
+ <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+ <bool name="config_doubleTapPowerGestureEnabled">true</bool>
<!-- Default target action for double tap of the power button gesture.
0: Launch camera
1: Launch wallet -->
@@ -7251,4 +7271,13 @@
POLICY_DOZE can also dim the screen unless parameter useNormalBrightnessForDoze of
DreamService#setDozeScreenState requests an exception. -->
<bool name="config_allowNormalBrightnessForDozePolicy">false</bool>
+
+ <!-- List of protected packages that require biometric authentication for modification
+ (Disable, force-stop or uninstalling updates). -->
+ <string-array name="config_biometric_protected_package_names" translatable="false" />
+
+ <!-- Package name of the on-device intelligent processor for vendor specific
+ features. Examples include the search functionality or the app
+ predictor. -->
+ <string name="config_systemVendorIntelligence" translatable="false"></string>
</resources>
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 64483f1f32db..648fe9078c40 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -38,4 +38,51 @@
<!-- Style the scrollbars accoridngly. -->
<drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_handle_material</drawable>
<drawable name="config_scrollbarTrackVertical">@null</drawable>
+
+ <!--
+ Material motion physics configs
+ values from https://carbon.googleplex.com/google-material-3/pages/motion/how-it-works/1d566b15-2923-4e40-bd1e-25a867b96cbb#7520e861-2251-4ddb-af33-59df0d233d21
+ -->
+ <!-- standard -->
+ <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+ <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardFastEffectStiffness">3800</integer>
+
+ <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultSpatialStiffness">700</integer>
+ <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardDefaultEffectStiffness">1600</integer>
+
+ <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowSpatialStiffness">300</integer>
+ <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionStandardSlowEffectStiffness">800</integer>
+
+
+ <!-- expressive -->
+ <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.6</item>
+ <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+ <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveFastEffectStiffness">3800</integer>
+
+ <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveDefaultSpatialStiffness">380</integer>
+ <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveDefaultEffectStiffness">1600</integer>
+
+ <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+ <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+ <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+ <integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/google-material-3/tokens/designSystems/20543ce18892f7d9/tokenSets/21c40db4e4f5af15
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">12dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">16dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">28dp</dimen>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e8063a27d77b..196da29127df 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -488,8 +488,16 @@
<java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
<!-- Whether to show the system notification to users whenever there is a change
- in the satellite availability state at the current location. -->
+ in the satellite availability state at the current location. -->
<bool name="config_satellite_should_notify_availability">true</bool>
<java-symbol type="bool" name="config_satellite_should_notify_availability" />
+ <!-- Whether to allow check message datagrams to be sent even when the satellite modem is in
+ not connected state. -->
+ <bool name="config_satellite_allow_check_message_in_not_connected">false</bool>
+ <java-symbol type="bool" name="config_satellite_allow_check_message_in_not_connected" />
+
+ <!-- Whether to allow TN scanning during satellite session. -->
+ <bool name="config_satellite_allow_tn_scanning_during_satellite_session">true</bool>
+ <java-symbol type="bool" name="config_satellite_allow_tn_scanning_during_satellite_session" />
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c8df662eed6d..f53acbfac71d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -255,6 +255,19 @@
This represents 16dp for the left margin + 40dp for the icon + 16dp for the right margin -->
<dimen name="notification_2025_content_margin_start">72dp</dimen>
+ <!-- The margin on the start of the media actions, selected to ensure that action icons which
+ are visually 12x12 in a 24x24 drawable will align correctly with the text. This means that
+ stock media action icons will align, but icons may be visually up to 20x20 and remain in-spec,
+ in which case they will protrude into the start column slightly.
+ 72dp (content margin) - 8dp (media action padding) - 6dp (visual padding within drawable) -->
+ <dimen name="notification_2025_media_actions_margin_start">58dp</dimen>
+
+ <!-- The margin on the start of notification actions (2025 redesign version), to align them to
+ the rest of the notification content. Note that this can be set to 0 if the actions would not
+ fit with it included.
+ 72dp (content margin) - 12dp (action padding) - 4dp (button inset) -->
+ <dimen name="notification_2025_actions_margin_start">56dp</dimen>
+
<!-- The margin on the end of most content views (ignores the expander) -->
<dimen name="notification_content_margin_end">16dp</dimen>
@@ -377,6 +390,9 @@
<!-- 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>
+
<!-- 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>
@@ -388,10 +404,19 @@
<!-- The height of each of the 1 or 2 lines in the headerless notification template -->
<dimen name="notification_headerless_line_height">24dp</dimen>
- <!-- vertical margin for the headerless notification content -->
+ <!-- The minimum height of the notification content (even when there's only one line of text) -->
+ <dimen name="notification_2025_content_min_height">40dp</dimen>
+
+ <!-- Height of a headerless notification with one or two lines -->
+ <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) -->
+ <dimen name="notification_2025_min_height">72dp</dimen>
+
+ <!-- Height of a headerless notification with one line -->
+ <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
<dimen name="notification_headerless_min_height">56dp</dimen>
- <!-- Height of a small notification in the status bar -->
+ <!-- Height of a small two-line notification -->
+ <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
<dimen name="notification_min_height">88dp</dimen>
<!-- The width of the big icons in notifications. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index e75371d6bf46..6c73b0c45a41 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -135,6 +135,8 @@
<public name="pageSizeCompat" />
<!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
<public name="shareRolePriority"/>
+ <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+ <public name="minSdkVersionFull"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01b60000">
@@ -149,9 +151,49 @@
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
@hide @SystemApi -->
<public name="config_systemDependencyInstaller" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
+ @hide @SystemApi -->
+ <public name="config_defaultReservedForTestingProfileGroupExclusivity" />
+ <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED)
+ @hide @SystemApi -->
+ <public name="config_systemVendorIntelligence" />
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01b30000">
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowSpatialDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXsmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusSmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusMedium"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusLarge"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXlarge"/>
</staging-public-group>
<staging-public-group type="color" first-id="0x01b20000">
@@ -203,6 +245,30 @@
</staging-public-group>
<staging-public-group type="integer" first-id="0x01aa0000">
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardFastEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardDefaultEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionStandardSlowEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveFastEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveDefaultEffectStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowSpatialStiffness"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+ <public name="config_motionExpressiveSlowEffectStiffness"/>
</staging-public-group>
<staging-public-group type="transition" first-id="0x01a90000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d498b9191559..cfc3ddca27eb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3135,6 +3135,86 @@
in <xliff:g id="count">%d</xliff:g>y
</string>
+ <!-- Phrase describing a time duration using minutes that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_minutes_shortest_past">
+ <xliff:g id="count">%d</xliff:g>m ago
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_hours_shortest_past">
+ <xliff:g id="count">%d</xliff:g>h ago
+ </string>
+
+ <!-- Phrase describing a time duration using days that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_days_shortest_past">
+ <xliff:g example="1" id="count">%d</xliff:g>d ago
+ </string>
+
+ <!-- Phrase describing a time duration using years that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+ <string name="duration_years_shortest_past">
+ <xliff:g id="count">%d</xliff:g>y ago
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_minutes_medium">
+ <xliff:g id="count">%d</xliff:g>min
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_hours_medium">
+ <xliff:g id="count">%d</xliff:g>hr
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_days_medium">
+ <xliff:g id="count">%d</xliff:g>d
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+ <string name="duration_years_medium">
+ <xliff:g id="count">%d</xliff:g>yr
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_minutes_medium_future">
+ in <xliff:g id="count">%d</xliff:g>min
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_hours_medium_future">
+ in <xliff:g id="count">%d</xliff:g>hr
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_days_medium_future">
+ in <xliff:g example="1" id="count">%d</xliff:g>d
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_years_medium_future">
+ in <xliff:g id="count">%d</xliff:g>yr
+ </string>
+
+ <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_minutes_medium_past">
+ <xliff:g id="count">%d</xliff:g>min ago
+ </string>
+
+ <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_hours_medium_past">
+ <xliff:g id="count">%d</xliff:g>hr ago
+ </string>
+
+ <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_days_medium_past">
+ <xliff:g example="1" id="count">%d</xliff:g>d ago
+ </string>
+
+ <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+ <string name="duration_years_medium_past">
+ <xliff:g id="count">%d</xliff:g>yr ago
+ </string>
+
<!-- Phrase describing a relative time using minutes in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
<string name="duration_minutes_relative">{count, plural,
=1 {# minute ago}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2f82011726ec..28de553f6063 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1797,6 +1797,7 @@
<java-symbol type="bool" name="config_customUserSwitchUi" />
<java-symbol type="bool" name="config_canRemoveFirstAccount" />
<java-symbol type="string" name="config_accountTypeToKeepFirstAccount" />
+ <java-symbol type="bool" name="config_allowDockBeforeProvision" />
<java-symbol type="bool" name="config_deskDockEnablesAccelerometer" />
<java-symbol type="bool" name="config_disableMenuKeyInLockScreen" />
<java-symbol type="bool" name="config_enableCarDockHomeLaunch" />
@@ -2012,6 +2013,8 @@
<java-symbol type="array" name="config_locationProviderPackageNames" />
<java-symbol type="array" name="config_locationDriverAssistancePackageNames" />
<java-symbol type="array" name="config_locationExtraPackageNames" />
+ <java-symbol type="string" name="config_populationDensityProviderPackageName" />
+ <java-symbol type="bool" name="config_enablePopulationDensityProviderOverlay" />
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
@@ -2054,6 +2057,7 @@
<java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
<java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
<java-symbol type="bool" name="config_keepDreamingWhenUnplugging" />
+ <java-symbol type="bool" name="config_glanceableHubEnabled" />
<java-symbol type="integer" name="config_keyguardDrawnTimeout" />
<java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
@@ -2395,6 +2399,12 @@
<java-symbol type="layout" name="notification_2025_template_header" />
<java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
<java-symbol type="layout" name="notification_2025_template_collapsed_media" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_big_picture" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_inbox" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_media" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_big_text" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_messaging" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_progress" />
<java-symbol type="layout" name="notification_template_material_base" />
<java-symbol type="layout" name="notification_template_material_heads_up_base" />
<java-symbol type="layout" name="notification_template_material_compact_heads_up_base" />
@@ -2406,6 +2416,8 @@
<java-symbol type="layout" name="notification_template_material_big_media" />
<java-symbol type="layout" name="notification_template_material_big_text" />
<java-symbol type="layout" name="notification_template_material_progress" />
+ <java-symbol type="layout" name="notification_template_material_messaging" />
+ <java-symbol type="layout" name="notification_template_material_big_messaging" />
<java-symbol type="layout" name="notification_template_header" />
<java-symbol type="layout" name="notification_material_media_action" />
<java-symbol type="color" name="notification_progress_background_color" />
@@ -3149,9 +3161,8 @@
<java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
- <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
<java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
- <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" />
+ <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
<java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
@@ -3345,8 +3356,6 @@
<java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
<java-symbol type="layout" name="app_anr_dialog" />
- <java-symbol type="layout" name="notification_template_material_messaging" />
- <java-symbol type="layout" name="notification_template_material_big_messaging" />
<java-symbol type="id" name="aerr_wait" />
@@ -3358,6 +3367,23 @@
<java-symbol type="string" name="duration_hours_shortest_future" />
<java-symbol type="string" name="duration_days_shortest_future" />
<java-symbol type="string" name="duration_years_shortest_future" />
+ <java-symbol type="string" name="duration_minutes_shortest_past" />
+ <java-symbol type="string" name="duration_hours_shortest_past" />
+ <java-symbol type="string" name="duration_days_shortest_past" />
+ <java-symbol type="string" name="duration_years_shortest_past" />
+
+ <java-symbol type="string" name="duration_minutes_medium" />
+ <java-symbol type="string" name="duration_hours_medium" />
+ <java-symbol type="string" name="duration_days_medium" />
+ <java-symbol type="string" name="duration_years_medium" />
+ <java-symbol type="string" name="duration_minutes_medium_future" />
+ <java-symbol type="string" name="duration_hours_medium_future" />
+ <java-symbol type="string" name="duration_days_medium_future" />
+ <java-symbol type="string" name="duration_years_medium_future" />
+ <java-symbol type="string" name="duration_minutes_medium_past" />
+ <java-symbol type="string" name="duration_hours_medium_past" />
+ <java-symbol type="string" name="duration_days_medium_past" />
+ <java-symbol type="string" name="duration_years_medium_past" />
<java-symbol type="string" name="duration_minutes_relative" />
<java-symbol type="string" name="duration_hours_relative" />
@@ -3448,6 +3474,8 @@
<!-- Notifications: CallStyle -->
<java-symbol type="layout" name="notification_template_material_call" />
<java-symbol type="layout" name="notification_template_material_big_call" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_call" />
+ <java-symbol type="layout" name="notification_2025_template_expanded_call" />
<java-symbol type="string" name="call_notification_answer_action" />
<java-symbol type="string" name="call_notification_answer_video_action" />
<java-symbol type="string" name="call_notification_decline_action" />
@@ -3472,6 +3500,7 @@
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
+ <java-symbol type="dimen" name="notification_2025_actions_margin_start"/>
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_actions_padding_start"/>
<java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
@@ -4099,6 +4128,7 @@
<java-symbol type="layout" name="notification_template_messaging_text_message" />
<java-symbol type="layout" name="notification_template_messaging_image_message" />
<java-symbol type="layout" name="notification_template_messaging_group" />
+ <java-symbol type="layout" name="notification_2025_messaging_group" />
<java-symbol type="id" name="message_text" />
<java-symbol type="id" name="message_name" />
<java-symbol type="id" name="message_icon" />
@@ -4629,9 +4659,11 @@
<java-symbol type="dimen" name="conversation_icon_container_top_padding" />
<java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />
<java-symbol type="layout" name="notification_template_material_conversation" />
+ <java-symbol type="layout" name="notification_2025_template_conversation" />
<java-symbol type="dimen" name="button_padding_horizontal_material" />
<java-symbol type="dimen" name="button_inset_horizontal_material" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
+ <java-symbol type="layout" name="notification_2025_conversation_face_pile_layout" />
<java-symbol type="string" name="unread_convo_overflow" />
<java-symbol type="drawable" name="conversation_badge_background" />
<java-symbol type="drawable" name="conversation_badge_ring" />
@@ -5811,5 +5843,46 @@
<!-- Style for Wear Material3 Button. Will only be used for sdk 36 or above. -->
<java-symbol type="style" name="Widget.DeviceDefault.Button.WearMaterial3" />
+ <!-- Style for Wear Material3 AlertDialog. Will only be used for sdk 36 or above. -->
+ <java-symbol type="style" name="AlertDialog.DeviceDefault.WearMaterial3" />
+
<java-symbol type="bool" name="config_allowNormalBrightnessForDozePolicy" />
+
+ <!-- Material motion spec config tokens -->
+ <java-symbol type="integer" name="config_motionStandardFastSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardFastEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardDefaultSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardDefaultEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardSlowSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionStandardSlowEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveFastSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveFastEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveDefaultSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveDefaultEffectStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveSlowSpatialStiffness"/>
+ <java-symbol type="integer" name="config_motionExpressiveSlowEffectStiffness"/>
+ <java-symbol type="dimen" name="config_motionStandardFastSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardFastEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardDefaultSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardDefaultEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardSlowSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionStandardSlowEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveFastSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveFastEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveDefaultSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveDefaultEffectDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveSlowSpatialDamping"/>
+ <java-symbol type="dimen" name="config_motionExpressiveSlowEffectDamping"/>
+
+ <!-- List of protected packages that require biometric authentication for modification -->
+ <java-symbol type="array" name="config_biometric_protected_package_names" />
+
+ <!-- Material shape spec config tokens -->
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXsmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusSmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusMedium"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/>
+
+
</resources>
diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp
index ae04aa4b5576..962ff3c0a6e0 100644
--- a/core/tests/FileSystemUtilsTest/Android.bp
+++ b/core/tests/FileSystemUtilsTest/Android.bp
@@ -17,14 +17,40 @@ package {
default_team: "trendy_team_android_kernel",
}
-cc_library {
- name: "libpunchtest",
+cc_defaults {
+ name: "libpunch_defaults",
stl: "none",
host_supported: true,
srcs: ["jni/android_test_jni_source.cpp"],
header_libs: ["jni_headers"],
}
+cc_library {
+ name: "libpunchtest",
+ defaults: ["libpunch_defaults"],
+}
+
+cc_library {
+ name: "libpunchtest_4kb",
+ defaults: ["libpunch_defaults"],
+ ldflags: ["-z max-page-size=0x1000"],
+}
+
+android_test_helper_app {
+ name: "app_with_4kb_elf",
+ srcs: ["app_with_4kb_elf/src/**/*.java"],
+ manifest: "app_with_4kb_elf/app_with_4kb_elf.xml",
+ compile_multilib: "64",
+ jni_libs: [
+ "libpunchtest_4kb",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ ],
+ use_embedded_native_libs: true,
+}
+
android_test_helper_app {
name: "embedded_native_libs_test_app",
srcs: ["apk_embedded_native_libs/src/**/*.java"],
@@ -72,6 +98,7 @@ java_test_host {
device_common_data: [
":embedded_native_libs_test_app",
":extract_native_libs_test_app",
+ ":app_with_4kb_elf",
],
test_suites: ["general-tests"],
test_config: "AndroidTest.xml",
diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml
index 27f49b2289ba..651a7ca15dac 100644
--- a/core/tests/FileSystemUtilsTest/AndroidTest.xml
+++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml
@@ -22,6 +22,7 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="embedded_native_libs_test_app.apk" />
<option name="test-file-name" value="extract_native_libs_test_app.apk" />
+ <option name="test-file-name" value="app_with_4kb_elf.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
new file mode 100644
index 000000000000..b9d6d4db2c81
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.test.pagesizecompat">
+ <application
+ android:extractNativeLibs="false"
+ android:pageSizeCompat="enabled">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true"
+ android:process=":NewProcess">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.pagesizecompat"/>
+</manifest> \ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
new file mode 100644
index 000000000000..893f9cd01497
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.test.pagesizecompat;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+
+public class MainActivity extends Activity {
+
+ static {
+ System.loadLibrary("punchtest_4kb");
+ }
+
+ @VisibleForTesting
+ static final String INTENT_TYPE = "android.test.pagesizecompat.EMBEDDED_4KB_LIB_LOADED";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_1 = "OP1";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_2 = "OP2";
+
+ @VisibleForTesting
+ static final String KEY_RESULT = "RESULT";
+
+ @Override
+ public void onCreate(Bundle savedOnstanceState) {
+ super.onCreate(savedOnstanceState);
+
+ Intent received = getIntent();
+ int op1 = received.getIntExtra(KEY_OPERAND_1, -1);
+ int op2 = received.getIntExtra(KEY_OPERAND_2, -1);
+ int result = add(op1, op2);
+
+ // Send broadcast so that test can know app has launched and lib is loaded
+ // attach result which has been fetched from JNI lib
+ Intent intent = new Intent(INTENT_TYPE);
+ intent.putExtra(KEY_RESULT, result);
+ sendBroadcast(intent);
+ }
+
+ private native int add(int op1, int op2);
+}
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
new file mode 100644
index 000000000000..9cbe414a0993
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.test.pagesizecompat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class PageSizeCompatTest {
+
+ @Test
+ public void testPageSizeCompat_embedded4KbLib() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ CountDownLatch receivedSignal = new CountDownLatch(1);
+
+ // Test app is expected to receive this and perform addition of operands using ELF
+ // loaded in compat mode on 16 KB device
+ int op1 = 48;
+ int op2 = 75;
+ IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE);
+ BroadcastReceiver broadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ receivedSignal.countDown();
+ int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000);
+ Assert.assertEquals(result, op1 + op2);
+
+ }
+ };
+ context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+ Intent launchIntent =
+ context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1);
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2);
+ context.startActivity(launchIntent);
+
+ Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS));
+ }
+}
diff --git a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
index 2a5ba817d9db..5bcd0b6ac144 100644
--- a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
+++ b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
@@ -22,6 +22,12 @@ jint JNICALL Java_android_test_embedded_MainActivity_add(JNIEnv*, jclass, jint o
return op1 + op2;
}
+extern "C" JNIEXPORT
+jint JNICALL Java_android_test_pagesizecompat_MainActivity_add(JNIEnv*, jclass, jint op1, jint op2)
+{
+ return op1 + op2;
+}
+
// This will be called from extract_native_libs_test_app
extern "C" JNIEXPORT
jint JNICALL Java_android_test_extract_MainActivity_subtract(JNIEnv*, jclass, jint op1, jint op2) {
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
index 77802e5e811a..aed907a0242f 100644
--- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -47,4 +47,13 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
assertTrue(isPackageInstalled(appPackage));
runDeviceTests(appPackage, appPackage + "." + testName);
}
+
+ @Test
+ @AppModeFull
+ public void runAppWith4KbLib_overrideCompatMode() throws DeviceNotAvailableException {
+ String appPackage = "android.test.pagesizecompat";
+ String testName = "PageSizeCompatTest";
+ assertTrue(isPackageInstalled(appPackage));
+ runDeviceTests(appPackage, appPackage + "." + testName);
+ }
}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 49425572b256..c67a0f9659f0 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -530,14 +530,25 @@ test_module_config {
}
test_module_config {
- name: "FrameworksCoreTests_internal_os_binder",
+ name: "FrameworksCoreTests_all_binder",
base: "FrameworksCoreTests",
test_suites: [
"automotive-tests",
"device-tests",
"device-platinum-tests",
],
- include_filters: ["com.android.internal.os.BinderDeathDispatcherTest"],
+ include_filters: [
+ "android.os.BinderProxyTest",
+ "android.os.BinderDeathRecipientTest",
+ "android.os.BinderFrozenStateChangeNotificationTest",
+ "android.os.BinderProxyCountingTest",
+ "android.os.BinderUncaughtExceptionHandlerTest",
+ "android.os.BinderThreadPriorityTest",
+ "android.os.BinderWorkSourceTest",
+ "android.os.ParcelNullabilityTest",
+ "android.os.ParcelTest",
+ "com.android.internal.os.BinderDeathDispatcherTest",
+ ],
exclude_annotations: ["com.android.internal.os.SkipPresubmit"],
}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 63e678d9ee53..9effeec23890 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -467,12 +467,25 @@ public class NotificationTest {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_notOngoing() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
public void testHasPromotableCharacteristics_wrongStyle() {
Notification n = new Notification.Builder(mContext, "test")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -480,6 +493,7 @@ public class NotificationTest {
.setContentTitle("TITLE")
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isFalse();
}
@@ -491,6 +505,7 @@ public class NotificationTest {
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isFalse();
}
@@ -503,6 +518,7 @@ public class NotificationTest {
.setStyle(new Notification.BigTextStyle())
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isFalse();
}
@@ -515,6 +531,7 @@ public class NotificationTest {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setGroup("someGroup")
.setGroupSummary(true)
.build();
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 177c7f0b2f27..bd273377984d 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -740,5 +740,20 @@ public class PropertyInvalidatedCacheTests {
assertEquals(null, cache.query(30));
// The recompute is 4 because nulls were not cached.
assertEquals(4, cache.getRecomputeCount());
+
+ // Verify that the default is not to cache nulls.
+ cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).api("testCachingNulls"),
+ new TestQuery());
+ cache.invalidateCache();
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ // The recompute is 4 because nulls were not cached.
+ assertEquals(4, cache.getRecomputeCount());
}
}
diff --git a/core/tests/coretests/src/android/app/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java
index 230c9e86db6b..6bd9b6a1453f 100644
--- a/core/tests/coretests/src/android/app/QueuedWorkTest.java
+++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java
@@ -163,18 +163,18 @@ public class QueuedWorkTest {
@Test
public void testHasPendingWork() {
- Semaphore releaser = new Semaphore(0);
- mQueuedWork.queue(
- () -> {
- try {
- releaser.acquire();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }, false);
+ final Semaphore releaser1 = new Semaphore(0);
+ final Semaphore releaser2 = new Semaphore(0);
+ mQueuedWork.queue(() -> releaser1.acquireUninterruptibly(), false);
+ mQueuedWork.queue(() -> releaser2.release(), false);
+ // Worker should be waiting for releaser1,
+ // and have pending work to release releaser2
assertThat(mQueuedWork.hasPendingWork()).isTrue();
- releaser.release();
- mQueuedWork.waitToFinish();
+
+ // Allow worker to get to releasing releaser2
+ releaser1.release();
+ releaser2.acquireUninterruptibly();
+ // If we got here then there is no pending work.
assertThat(mQueuedWork.hasPendingWork()).isFalse();
}
} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 8d045f87063b..1f1000f2800d 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -49,6 +49,7 @@ import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.app.ResourcesManager;
+import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
@@ -79,6 +80,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
+import android.view.Surface;
import android.view.View;
import android.window.ActivityWindowInfo;
import android.window.WindowContextInfo;
@@ -302,6 +304,59 @@ public class ActivityThreadTest {
assertScreenScale(originalScale, app, originalAppConfig, originalAppMetrics);
}
+ @Test
+ public void testOverrideDisplayRotation() throws Exception {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+ final Application app = activity.getApplication();
+ final ActivityThread activityThread = activity.getActivityThread();
+ final IApplicationThread appThread = activityThread.getApplicationThread();
+ final Configuration originalAppConfig =
+ new Configuration(app.getResources().getConfiguration());
+ final int originalDisplayRotation = originalAppConfig.windowConfiguration
+ .getDisplayRotation();
+
+ final Configuration newConfig = new Configuration(originalAppConfig);
+ newConfig.seq = BASE_SEQ + 1;
+
+ int sandboxedDisplayRotation = (originalDisplayRotation + 1) % 4;
+ CompatibilityInfo.setOverrideDisplayRotation(sandboxedDisplayRotation);
+ try {
+ // Send process level config change.
+ ClientTransaction transaction = newTransaction(activityThread);
+ transaction.addTransactionItem(
+ new ConfigurationChangeItem(newConfig, DEVICE_ID_INVALID));
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertDisplayRotation(sandboxedDisplayRotation, app);
+
+ sandboxedDisplayRotation = (sandboxedDisplayRotation + 1) % 4;
+ CompatibilityInfo.setOverrideDisplayRotation(sandboxedDisplayRotation);
+ // Send activity level config change.
+ newConfig.seq++;
+ transaction = newTransaction(activityThread);
+ transaction.addTransactionItem(new ActivityConfigurationChangeItem(
+ activity.getActivityToken(), newConfig, new ActivityWindowInfo()));
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertDisplayRotation(sandboxedDisplayRotation, activity);
+
+ // Execute a local relaunch item with current scaled config (e.g. simulate recreate),
+ // the config should not change again.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activityThread.executeTransaction(
+ newRelaunchResumeTransaction(activity)));
+
+ assertDisplayRotation(sandboxedDisplayRotation, activity);
+ } finally {
+ CompatibilityInfo.setOverrideDisplayRotation(WindowConfiguration.ROTATION_UNDEFINED);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> restoreConfig(activityThread, originalAppConfig));
+ }
+ assertDisplayRotation(originalDisplayRotation, app);
+ }
+
private static void assertScreenScale(float scale, Context context,
Configuration origConfig, DisplayMetrics origMetrics) {
final int expectedDpi = (int) (origConfig.densityDpi * scale + .5f);
@@ -326,6 +381,12 @@ public class ActivityThreadTest {
assertEquals(expectedMaxBounds, currentConfig.windowConfiguration.getMaxBounds());
}
+ private static void assertDisplayRotation(@Surface.Rotation int expectedRotation,
+ Context context) {
+ final Configuration currentConfig = context.getResources().getConfiguration();
+ assertEquals(expectedRotation, currentConfig.windowConfiguration.getDisplayRotation());
+ }
+
@Test
public void testHandleActivityConfigurationChanged() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
diff --git a/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
index 01c2abf2781b..a22eae3d19d9 100644
--- a/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
+++ b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
@@ -15,13 +15,20 @@
*/
package android.app.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 com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.content.ComponentName;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Parcel;
import android.os.PersistableBundle;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.modules.utils.TypedXmlPullParser;
@@ -41,17 +48,20 @@ import java.util.List;
@RunWith(JUnit4.class)
public class WallpaperDescriptionTest {
- private static final String TAG = "WallpaperDescriptionTest";
+ private static final Rect DEFAULT_CROP_PORTRAIT = new Rect(1, 2, 3, 4);
+ private static final Rect DEFAULT_CROP_LANDSCAPE = new Rect(5, 6, 7, 8);
+ private static final Rect DEFAULT_CROP_SQUARE_PORTRAIT = new Rect(9, 10, 11, 12);
+ private static final Rect DEFAULT_CROP_SQUARE_LANDSCAPE = new Rect(13, 14, 15, 16);
private final ComponentName mTestComponent = new ComponentName("fakePackage", "fakeClass");
@Test
public void equals_ignoresIrrelevantFields() {
String id = "fakeId";
- WallpaperDescription desc1 = new WallpaperDescription.Builder().setComponent(
- mTestComponent).setId(id).setTitle("fake one").build();
- WallpaperDescription desc2 = new WallpaperDescription.Builder().setComponent(
- mTestComponent).setId(id).setTitle("fake different").build();
+ WallpaperDescription desc1 = new WallpaperDescription.Builder()
+ .setComponent(mTestComponent).setId(id).setTitle("fake one").build();
+ WallpaperDescription desc2 = new WallpaperDescription.Builder()
+ .setComponent(mTestComponent).setId(id).setTitle("fake different").build();
assertThat(desc1).isEqualTo(desc2);
}
@@ -72,13 +82,21 @@ public class WallpaperDescriptionTest {
final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
final List<CharSequence> description = List.of("line1", "line2");
final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
- final PersistableBundle content = new PersistableBundle();
- content.putString("ckey", "cvalue");
+ final PersistableBundle content = makeDefaultContent();
+ final SparseArray<Rect> cropHints = makeDefaultCropHints();
+ final float sampleSize = 0.9f;
WallpaperDescription source = new WallpaperDescription.Builder()
- .setComponent(mTestComponent).setId("fakeId").setThumbnail(thumbnail)
- .setTitle("Fake title").setDescription(description)
- .setContextUri(contextUri).setContextDescription("Context description")
- .setContent(content).build();
+ .setComponent(mTestComponent)
+ .setId("fakeId")
+ .setThumbnail(thumbnail)
+ .setTitle("Fake title")
+ .setDescription(description)
+ .setContextUri(contextUri)
+ .setContextDescription("Context description")
+ .setContent(content)
+ .setCropHints(cropHints)
+ .setSampleSize(sampleSize)
+ .build();
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
TypedXmlSerializer serializer = Xml.newBinarySerializer();
@@ -122,6 +140,57 @@ public class WallpaperDescriptionTest {
assertThat(destination.getContent()).isNotNull();
assertThat(destination.getContent().getString("ckey")).isEqualTo(
source.getContent().getString("ckey"));
+ assertThat(destination.getCropHints()).isNotNull();
+ assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_LANDSCAPE);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_SQUARE_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_SQUARE_LANDSCAPE);
+ assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
+ }
+
+ @Test
+ public void xml_roundTripSucceeds_withNulls() throws IOException, XmlPullParserException {
+ WallpaperDescription source = new WallpaperDescription.Builder().build();
+
+ ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newBinarySerializer();
+ serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, "test");
+ source.saveToXml(serializer);
+ serializer.endTag(null, "test");
+ serializer.endDocument();
+ ostream.close();
+
+ WallpaperDescription destination = null;
+ ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ parser.setInput(istream, StandardCharsets.UTF_8.name());
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG && "test".equals(parser.getName())) {
+ destination = WallpaperDescription.restoreFromXml(parser);
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+
+ assertThat(destination).isNotNull();
+ assertThat(destination.getComponent()).isEqualTo(source.getComponent());
+ assertThat(destination.getId()).isEqualTo(source.getId());
+ assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
+ assertThat(destination.getTitle()).isNull();
+ assertThat(destination.getDescription()).hasSize(0);
+ assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
+ assertThat(destination.getContextDescription()).isNull();
+ assertThat(destination.getContent()).isNotNull();
+ assertThat(destination.getContent().keySet()).isEmpty();
+ assertThat(destination.getCropHints()).isNotNull();
+ assertThat(destination.getCropHints().size()).isEqualTo(0);
+ assertThat(destination.getSampleSize()).isEqualTo(source.getSampleSize());
}
@Test
@@ -129,13 +198,21 @@ public class WallpaperDescriptionTest {
final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
final List<CharSequence> description = List.of("line1", "line2");
final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
- final PersistableBundle content = new PersistableBundle();
- content.putString("ckey", "cvalue");
- WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
- mTestComponent).setId("fakeId").setThumbnail(thumbnail).setTitle(
- "Fake title").setDescription(description).setContextUri(
- contextUri).setContextDescription("Context description").setContent(
- content).build();
+ final PersistableBundle content = makeDefaultContent();
+ final SparseArray<Rect> cropHints = makeDefaultCropHints();
+ final float sampleSize = 0.9f;
+ WallpaperDescription source = new WallpaperDescription.Builder()
+ .setComponent(mTestComponent)
+ .setId("fakeId")
+ .setThumbnail(thumbnail)
+ .setTitle("Fake title")
+ .setDescription(description)
+ .setContextUri(contextUri)
+ .setContextDescription("Context description")
+ .setContent(content)
+ .setCropHints(cropHints)
+ .setSampleSize(sampleSize)
+ .build();
Parcel parcel = Parcel.obtain();
source.writeToParcel(parcel, 0);
@@ -162,6 +239,16 @@ public class WallpaperDescriptionTest {
assertThat(destination.getContent()).isNotNull();
assertThat(destination.getContent().getString("ckey")).isEqualTo(
source.getContent().getString("ckey"));
+ assertThat(destination.getCropHints()).isNotNull();
+ assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_LANDSCAPE);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_SQUARE_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_SQUARE_LANDSCAPE);
+ assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
}
@Test
@@ -178,17 +265,14 @@ public class WallpaperDescriptionTest {
assertThat(destination.getId()).isEqualTo(source.getId());
assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
assertThat(destination.getTitle()).isNull();
- assertThat(destination.getDescription()).hasSize(source.getDescription().size());
- for (int i = 0; i < destination.getDescription().size(); i++) {
- CharSequence strDest = destination.getDescription().get(i);
- CharSequence strSrc = source.getDescription().get(i);
- assertWithMessage("description string mismatch")
- .that(CharSequence.compare(strDest, strSrc)).isEqualTo(0);
- }
+ assertThat(destination.getDescription()).hasSize(0);
assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
assertThat(destination.getContextDescription()).isNull();
assertThat(destination.getContent()).isNotNull();
assertThat(destination.getContent().keySet()).isEmpty();
+ assertThat(destination.getCropHints()).isNotNull();
+ assertThat(destination.getCropHints().size()).isEqualTo(0);
+ assertThat(destination.getSampleSize()).isEqualTo(source.getSampleSize());
}
@Test
@@ -197,14 +281,22 @@ public class WallpaperDescriptionTest {
final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
final List<CharSequence> description = List.of("line1", "line2");
final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
- final PersistableBundle content = new PersistableBundle();
- content.putString("ckey", "cvalue");
+ final PersistableBundle content = makeDefaultContent();
+ final SparseArray<Rect> cropHints = makeDefaultCropHints();
+ final float sampleSize = 1.1f;
final String destinationId = "destinationId";
- WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
- mTestComponent).setId(sourceId).setThumbnail(thumbnail).setTitle(
- "Fake title").setDescription(description).setContextUri(
- contextUri).setContextDescription("Context description").setContent(
- content).build();
+ WallpaperDescription source = new WallpaperDescription.Builder()
+ .setComponent(mTestComponent)
+ .setId(sourceId)
+ .setThumbnail(thumbnail)
+ .setTitle("Fake title")
+ .setDescription(description)
+ .setContextUri(contextUri)
+ .setContextDescription("Context description")
+ .setContent(content)
+ .setCropHints(cropHints)
+ .setSampleSize(sampleSize)
+ .build();
WallpaperDescription destination = source.toBuilder().setId(destinationId).build();
@@ -227,5 +319,29 @@ public class WallpaperDescriptionTest {
assertThat(destination.getContent()).isNotNull();
assertThat(destination.getContent().getString("ckey")).isEqualTo(
source.getContent().getString("ckey"));
+ assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_LANDSCAPE);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+ DEFAULT_CROP_SQUARE_PORTRAIT);
+ assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+ DEFAULT_CROP_SQUARE_LANDSCAPE);
+ assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
+ }
+
+ private static PersistableBundle makeDefaultContent() {
+ final PersistableBundle content = new PersistableBundle();
+ content.putString("ckey", "cvalue");
+ return content;
+ }
+
+ private static SparseArray<Rect> makeDefaultCropHints() {
+ final SparseArray<Rect> cropHints = new SparseArray<>();
+ cropHints.put(ORIENTATION_PORTRAIT, DEFAULT_CROP_PORTRAIT);
+ cropHints.put(ORIENTATION_LANDSCAPE, DEFAULT_CROP_LANDSCAPE);
+ cropHints.put(ORIENTATION_SQUARE_PORTRAIT, DEFAULT_CROP_SQUARE_PORTRAIT);
+ cropHints.put(ORIENTATION_SQUARE_LANDSCAPE, DEFAULT_CROP_SQUARE_LANDSCAPE);
+ return cropHints;
}
}
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index 7bc4abd935b6..fdfb0c34cdeb 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -19,8 +19,9 @@ package android.content;
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 android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -29,6 +30,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.security.Flags;
+import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -40,6 +42,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Build/Install/Run:
@@ -51,7 +54,6 @@ import java.util.List;
public class IntentTest {
private static final String TEST_ACTION = "android.content.IntentTest_test";
private static final String TEST_EXTRA_NAME = "testExtraName";
- private static final Uri TEST_URI = Uri.parse("content://com.example/people");
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,4 +131,111 @@ public class IntentTest {
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testFillInCreatorTokenInfo() {
+ // case 1: intent does not have creatorTokenInfo; fillinIntent contains creatorTokenInfo
+ Intent intent = new Intent();
+ Intent fillInIntent = new Intent();
+ fillInIntent.setCreatorToken(new Binder());
+ fillInIntent.putExtra("extraKey", new Intent());
+
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ // extra intent keys are merged
+ assertThat(intent.getExtraIntentKeys()).isEqualTo(fillInIntent.getExtraIntentKeys());
+ // but creator token is not overwritten.
+ assertThat(intent.getCreatorToken()).isNull();
+
+
+ // case 2: Both intent and fillInIntent contains creatorToken, intent's creatorToken is not
+ // overwritten.
+ intent = new Intent();
+ IBinder creatorToken = new Binder();
+ intent.setCreatorToken(creatorToken);
+ fillInIntent = new Intent();
+ fillInIntent.setCreatorToken(new Binder());
+
+ intent.fillIn(fillInIntent, 0);
+
+ assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);
+
+
+ // case 3: Contains duplicate extra keys
+ intent = new Intent();
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ Set originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ assertThat(intent.getExtraIntentKeys()).hasSize(3);
+ assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+ // case 4: Both contains a mixture of extras and clip data. NOT force to fill in clip data.
+ intent = new Intent();
+ ClipData clipData = ClipData.newIntent("clip", new Intent());
+ clipData.addItem(new ClipData.Item(new Intent()));
+ intent.setClipData(clipData);
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ ClipData fillInClipData = ClipData.newIntent("clip", new Intent());
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInIntent.setClipData(fillInClipData);
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, 0);
+
+ // size is 5 ( 3 extras merged from both + 2 clip data in the original.
+ assertThat(intent.getExtraIntentKeys()).hasSize(5);
+ // all keys from original are kept.
+ assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ // Not all keys from fillInIntent are kept - clip data keys are dropped.
+ assertFalse(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+ // case 5: Both contains a mixture of extras and clip data. Force to fill in clip data.
+ intent = new Intent();
+ clipData = ClipData.newIntent("clip", new Intent());
+ clipData.addItem(new ClipData.Item(new Intent()));
+ clipData.addItem(new ClipData.Item(new Intent()));
+ clipData.addItem(new ClipData.Item(new Intent()));
+ intent.setClipData(clipData);
+ intent.putExtra("key1", new Intent());
+ intent.putExtra("key2", new Intent());
+ fillInIntent = new Intent();
+ fillInClipData = ClipData.newIntent("clip", new Intent());
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInClipData.addItem(new ClipData.Item(new Intent()));
+ fillInIntent.setClipData(fillInClipData);
+ fillInIntent.putExtra("key1", new Intent());
+ fillInIntent.putExtra("key3", new Intent());
+
+ intent.collectExtraIntentKeys();
+ originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+ fillInIntent.collectExtraIntentKeys();
+ intent.fillIn(fillInIntent, Intent.FILL_IN_CLIP_DATA);
+
+ // size is 6 ( 3 extras merged from both + 3 clip data in the fillInIntent.
+ assertThat(intent.getExtraIntentKeys()).hasSize(6);
+ // all keys from fillInIntent are kept.
+ assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+ // Not all keys from intent are kept - clip data keys are dropped.
+ assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+ }
}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
index b60d61408054..c55008e4aba5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -24,6 +24,9 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PackageManagerTest {
@@ -46,4 +49,25 @@ public class PackageManagerTest {
public void testResolveInfoFlags() throws Exception {
assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
}
+
+ @Test
+ public void testSdkFeatureCount() throws Exception {
+ // Check to make sure the system feature `SdkConst` annotation processor yields sensible
+ // results. We don't care about the exactness, just that it's not pathologically wrong.
+ assertThat(PackageManager.SDK_FEATURE_COUNT).isGreaterThan(150);
+ assertThat(PackageManager.SDK_FEATURE_COUNT).isLessThan(500);
+ assertThat(PackageManager.SDK_FEATURE_COUNT)
+ .isWithin(50)
+ .of(getApproximateFeatureCountUsingReflection());
+ }
+
+ /* Return a ballpark estimate of the feature count using FEATURE_ field names. */
+ private static int getApproximateFeatureCountUsingReflection() {
+ return (int)
+ Arrays.stream(PackageManager.class.getFields())
+ .filter(field -> Modifier.isStatic(field.getModifiers()))
+ .filter(field -> Modifier.isFinal(field.getModifiers()))
+ .filter(field -> field.getName().startsWith("FEATURE_"))
+ .count();
+ }
}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index c0a9bc2cdd24..f9d449cd3b10 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -18,10 +18,7 @@ package android.content.res
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsEnabled
-import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider
-import android.platform.test.ravenwood.RavenwoodRule
import android.util.SparseArray
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,15 +44,7 @@ import org.junit.runner.RunWith
class FontScaleConverterFactoryTest {
@get:Rule
- val ravenwoodRule: RavenwoodRule = RavenwoodRule.Builder().build()
-
- @get:Rule
- val checkFlagsRule: CheckFlagsRule =
- if (RavenwoodRule.isOnRavenwood()) {
- RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- } else {
- DeviceFlagsValueProvider.createCheckFlagsRule()
- }
+ val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index be8ecbe66791..cfcd53e14c79 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -33,8 +33,6 @@ import android.platform.test.annotations.Postsubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -65,13 +63,7 @@ public class ResourcesManagerTest {
private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
@Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- RavenwoodRule.isOnRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private ResourcesManager mResourcesManager;
private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 6a5224d4524b..7a5b3064b3a3 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -55,6 +55,9 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
/**
* Tests for {@link DisplayManagerGlobal}.
*
@@ -79,10 +82,13 @@ public class DisplayManagerGlobalTest {
private IDisplayManager mDisplayManager;
@Mock
- private DisplayManager.DisplayListener mListener;
+ private DisplayManager.DisplayListener mDisplayListener;
+
+ @Mock
+ private DisplayManager.DisplayListener mDisplayListener2;
@Mock
- private DisplayManager.DisplayListener mListener2;
+ private Consumer<DisplayTopology> mTopologyListener;
@Captor
private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;
@@ -90,6 +96,7 @@ public class DisplayManagerGlobalTest {
private Context mContext;
private DisplayManagerGlobal mDisplayManagerGlobal;
private Handler mHandler;
+ private Executor mExecutor;
@Before
public void setUp() throws RemoteException {
@@ -97,13 +104,14 @@ public class DisplayManagerGlobalTest {
Mockito.when(mDisplayManager.getPreferredWideGamutColorSpaceId()).thenReturn(0);
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mHandler = mContext.getMainThreadHandler();
+ mExecutor = mContext.getMainExecutor();
mDisplayManagerGlobal = new DisplayManagerGlobal(mDisplayManager);
}
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
- null);
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -111,31 +119,31 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verify(mListener).onDisplayAdded(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayAdded(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
// Mock IDisplayManager to return a different display info to trigger display change.
final DisplayInfo newDisplayInfo = new DisplayInfo();
newDisplayInfo.rotation++;
doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verify(mListener).onDisplayRemoved(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayRemoved(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
public void testDisplayListenerIsCalled_WhenDisplayPropertyChangeEventOccurs()
throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| INTERNAL_EVENT_FLAG_DISPLAY_STATE,
null);
@@ -145,50 +153,50 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, EVENT_DISPLAY_STATE_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
- null);
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
int displayId = 1;
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
}
@Test
@@ -207,7 +215,7 @@ public class DisplayManagerGlobalTest {
@Test
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
null);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
@@ -228,7 +236,7 @@ public class DisplayManagerGlobalTest {
.registerCallbackWithEventMask(mCallbackCaptor.capture(),
eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
- mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+ mDisplayManagerGlobal.unregisterDisplayListener(mDisplayListener);
inOrder.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
}
@@ -244,33 +252,49 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(123);
// One listener listens on add/remove, and the other one listens on change.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
null /* packageName */);
- mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
null /* packageName */);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
- verify(mListener, never()).onDisplayChanged(anyInt());
- verify(mListener2).onDisplayChanged(321);
+ verify(mDisplayListener, never()).onDisplayChanged(anyInt());
+ verify(mDisplayListener2).onDisplayChanged(321);
// Trigger the callback again even if the display info is not changed.
- clearInvocations(mListener2);
+ clearInvocations(mDisplayListener2);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
- verify(mListener2).onDisplayChanged(321);
+ verify(mDisplayListener2).onDisplayChanged(321);
// No callback for non-existing display (no display info returned from IDisplayManager).
- clearInvocations(mListener2);
+ clearInvocations(mDisplayListener2);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(456);
waitForHandler();
- verify(mListener2, never()).onDisplayChanged(anyInt());
+ verify(mDisplayListener2, never()).onDisplayChanged(anyInt());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyListenerIsCalled_WhenTopologyUpdateOccurs() throws RemoteException {
+ mDisplayManagerGlobal.registerTopologyListener(mExecutor, mTopologyListener,
+ /* packageName= */ null);
+ Mockito.verify(mDisplayManager).registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED));
+ IDisplayManagerCallback callback = mCallbackCaptor.getValue();
+
+ DisplayTopology topology = new DisplayTopology();
+ callback.onTopologyChanged(topology);
+ waitForHandler();
+ Mockito.verify(mTopologyListener).accept(topology);
+ Mockito.verifyNoMoreInteractions(mTopologyListener);
}
@Test
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index 8969b2b72e77..18e4fde280ec 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -38,12 +38,7 @@ class DisplayTopologyTest {
topology.addDisplay(displayId, width, height)
assertThat(topology.primaryDisplayId).isEqualTo(displayId)
-
- val display = topology.root!!
- assertThat(display.displayId).isEqualTo(displayId)
- assertThat(display.width).isEqualTo(width)
- assertThat(display.height).isEqualTo(height)
- assertThat(display.children).isEmpty()
+ verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
}
@Test
@@ -62,18 +57,9 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
val display1 = topology.root!!
- assertThat(display1.displayId).isEqualTo(displayId1)
- assertThat(display1.width).isEqualTo(width1)
- assertThat(display1.height).isEqualTo(height1)
- assertThat(display1.children).hasSize(1)
-
- val display2 = display1.children[0]
- assertThat(display2.displayId).isEqualTo(displayId2)
- assertThat(display2.width).isEqualTo(width2)
- assertThat(display2.height).isEqualTo(height2)
- assertThat(display2.children).isEmpty()
- assertThat(display2.position).isEqualTo(POSITION_TOP)
- assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+ verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
+ verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+ offset = width1 / 2 - width2 / 2, noOfChildren = 0)
}
@Test
@@ -97,29 +83,18 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
val display1 = topology.root!!
- assertThat(display1.displayId).isEqualTo(displayId1)
- assertThat(display1.width).isEqualTo(width1)
- assertThat(display1.height).isEqualTo(height1)
- assertThat(display1.children).hasSize(1)
+ verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
val display2 = display1.children[0]
- assertThat(display2.displayId).isEqualTo(displayId2)
- assertThat(display2.width).isEqualTo(width2)
- assertThat(display2.height).isEqualTo(height2)
- assertThat(display2.children).hasSize(1)
- assertThat(display2.position).isEqualTo(POSITION_TOP)
- assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+ verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+ offset = width1 / 2 - width2 / 2, noOfChildren = 1)
var display = display2
for (i in 3..noOfDisplays) {
display = display.children[0]
- assertThat(display.displayId).isEqualTo(i)
- assertThat(display.width).isEqualTo(width1)
- assertThat(display.height).isEqualTo(height1)
// The last display should have no children
- assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.position).isEqualTo(POSITION_RIGHT)
- assertThat(display.offset).isEqualTo(0)
+ verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+ noOfChildren = if (i < noOfDisplays) 1 else 0)
}
}
@@ -147,18 +122,11 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
var display1 = topology.root!!
- assertThat(display1.displayId).isEqualTo(displayId1)
- assertThat(display1.width).isEqualTo(width1)
- assertThat(display1.height).isEqualTo(height1)
- assertThat(display1.children).hasSize(1)
+ verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
var display2 = display1.children[0]
- assertThat(display2.displayId).isEqualTo(displayId2)
- assertThat(display2.width).isEqualTo(width2)
- assertThat(display2.height).isEqualTo(height2)
- assertThat(display2.children).hasSize(1)
- assertThat(display2.position).isEqualTo(POSITION_TOP)
- assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+ verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
+ offset = width1 / 2 - width2 / 2, noOfChildren = 1)
var display = display2
for (i in 3..noOfDisplays) {
@@ -166,13 +134,9 @@ class DisplayTopologyTest {
continue
}
display = display.children[0]
- assertThat(display.displayId).isEqualTo(i)
- assertThat(display.width).isEqualTo(width1)
- assertThat(display.height).isEqualTo(height1)
// The last display should have no children
- assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.position).isEqualTo(POSITION_RIGHT)
- assertThat(display.offset).isEqualTo(0)
+ verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+ noOfChildren = if (i < noOfDisplays) 1 else 0)
}
topology.removeDisplay(22)
@@ -185,18 +149,11 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
display1 = topology.root!!
- assertThat(display1.displayId).isEqualTo(displayId1)
- assertThat(display1.width).isEqualTo(width1)
- assertThat(display1.height).isEqualTo(height1)
- assertThat(display1.children).hasSize(1)
+ verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
display2 = display1.children[0]
- assertThat(display2.displayId).isEqualTo(displayId2)
- assertThat(display2.width).isEqualTo(width2)
- assertThat(display2.height).isEqualTo(height2)
- assertThat(display2.children).hasSize(1)
- assertThat(display2.position).isEqualTo(POSITION_TOP)
- assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+ verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
+ offset = width1 / 2 - width2 / 2, noOfChildren = 1)
display = display2
for (i in 3..noOfDisplays) {
@@ -204,13 +161,9 @@ class DisplayTopologyTest {
continue
}
display = display.children[0]
- assertThat(display.displayId).isEqualTo(i)
- assertThat(display.width).isEqualTo(width1)
- assertThat(display.height).isEqualTo(height1)
// The last display should have no children
- assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.position).isEqualTo(POSITION_RIGHT)
- assertThat(display.offset).isEqualTo(0)
+ verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+ noOfChildren = if (i < noOfDisplays) 1 else 0)
}
}
@@ -237,12 +190,7 @@ class DisplayTopologyTest {
topology.removeDisplay(3)
assertThat(topology.primaryDisplayId).isEqualTo(displayId)
-
- val display = topology.root!!
- assertThat(display.displayId).isEqualTo(displayId)
- assertThat(display.width).isEqualTo(width)
- assertThat(display.height).isEqualTo(height)
- assertThat(display.children).isEmpty()
+ verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
}
@Test
@@ -258,11 +206,46 @@ class DisplayTopologyTest {
topology.removeDisplay(displayId2)
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
- val display = topology.root!!
- assertThat(display.displayId).isEqualTo(displayId1)
- assertThat(display.width).isEqualTo(width)
- assertThat(display.height).isEqualTo(height)
- assertThat(display.children).isEmpty()
+ verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
+ }
+
+ @Test
+ fun normalization_clampsOffsets() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 800f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_LEFT, /* offset= */ -300f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_TOP, /* offset= */ 1000f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ topology.normalize()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.root!!
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 600f, noOfChildren = 1)
+
+ val actualDisplay3 = actualDisplay1.children[1]
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_LEFT,
+ offset = -200f, noOfChildren = 0)
+
+ val actualDisplay4 = actualDisplay2.children[0]
+ verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_TOP,
+ offset = 600f, noOfChildren = 0)
}
@Test
@@ -289,34 +272,19 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
val actualDisplay1 = topology.root!!
- assertThat(actualDisplay1.displayId).isEqualTo(1)
- assertThat(actualDisplay1.width).isEqualTo(200f)
- assertThat(actualDisplay1.height).isEqualTo(600f)
- assertThat(actualDisplay1.children).hasSize(2)
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
val actualDisplay2 = actualDisplay1.children[0]
- assertThat(actualDisplay2.displayId).isEqualTo(2)
- assertThat(actualDisplay2.width).isEqualTo(600f)
- assertThat(actualDisplay2.height).isEqualTo(200f)
- assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.offset).isEqualTo(0f)
- assertThat(actualDisplay2.children).hasSize(1)
+ verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 1)
val actualDisplay3 = actualDisplay1.children[1]
- assertThat(actualDisplay3.displayId).isEqualTo(3)
- assertThat(actualDisplay3.width).isEqualTo(600f)
- assertThat(actualDisplay3.height).isEqualTo(200f)
- assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.offset).isEqualTo(400f)
- assertThat(actualDisplay3.children).isEmpty()
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 400f, noOfChildren = 0)
val actualDisplay4 = actualDisplay2.children[0]
- assertThat(actualDisplay4.displayId).isEqualTo(4)
- assertThat(actualDisplay4.width).isEqualTo(200f)
- assertThat(actualDisplay4.height).isEqualTo(600f)
- assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.offset).isEqualTo(0f)
- assertThat(actualDisplay4.children).isEmpty()
+ verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 0)
}
@Test
@@ -344,34 +312,19 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
val actualDisplay1 = topology.root!!
- assertThat(actualDisplay1.displayId).isEqualTo(1)
- assertThat(actualDisplay1.width).isEqualTo(200f)
- assertThat(actualDisplay1.height).isEqualTo(600f)
- assertThat(actualDisplay1.children).hasSize(1)
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 1)
val actualDisplay2 = actualDisplay1.children[0]
- assertThat(actualDisplay2.displayId).isEqualTo(2)
- assertThat(actualDisplay2.width).isEqualTo(200f)
- assertThat(actualDisplay2.height).isEqualTo(600f)
- assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.offset).isEqualTo(0f)
- assertThat(actualDisplay2.children).hasSize(2)
+ verifyDisplay(actualDisplay2, id = 2, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 2)
val actualDisplay3 = actualDisplay2.children[1]
- assertThat(actualDisplay3.displayId).isEqualTo(3)
- assertThat(actualDisplay3.width).isEqualTo(600f)
- assertThat(actualDisplay3.height).isEqualTo(200f)
- assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.offset).isEqualTo(10f)
- assertThat(actualDisplay3.children).isEmpty()
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 10f, noOfChildren = 0)
val actualDisplay4 = actualDisplay2.children[0]
- assertThat(actualDisplay4.displayId).isEqualTo(4)
- assertThat(actualDisplay4.width).isEqualTo(200f)
- assertThat(actualDisplay4.height).isEqualTo(600f)
- assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.offset).isEqualTo(210f)
- assertThat(actualDisplay4.children).isEmpty()
+ verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = 210f, noOfChildren = 0)
}
@Test
@@ -397,26 +350,15 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
val actualDisplay1 = topology.root!!
- assertThat(actualDisplay1.displayId).isEqualTo(1)
- assertThat(actualDisplay1.width).isEqualTo(200f)
- assertThat(actualDisplay1.height).isEqualTo(50f)
- assertThat(actualDisplay1.children).hasSize(1)
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 50f, noOfChildren = 1)
val actualDisplay2 = actualDisplay1.children[0]
- assertThat(actualDisplay2.displayId).isEqualTo(2)
- assertThat(actualDisplay2.width).isEqualTo(600f)
- assertThat(actualDisplay2.height).isEqualTo(200f)
- assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.offset).isEqualTo(0f)
- assertThat(actualDisplay2.children).hasSize(1)
+ verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 1)
val actualDisplay3 = actualDisplay2.children[0]
- assertThat(actualDisplay3.displayId).isEqualTo(3)
- assertThat(actualDisplay3.width).isEqualTo(600f)
- assertThat(actualDisplay3.height).isEqualTo(200f)
- assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM)
- assertThat(actualDisplay3.offset).isEqualTo(0f)
- assertThat(actualDisplay3.children).isEmpty()
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_BOTTOM,
+ offset = 0f, noOfChildren = 0)
}
@Test
@@ -443,34 +385,19 @@ class DisplayTopologyTest {
assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
val actualDisplay1 = topology.root!!
- assertThat(actualDisplay1.displayId).isEqualTo(1)
- assertThat(actualDisplay1.width).isEqualTo(200f)
- assertThat(actualDisplay1.height).isEqualTo(600f)
- assertThat(actualDisplay1.children).hasSize(1)
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 1)
val actualDisplay2 = actualDisplay1.children[0]
- assertThat(actualDisplay2.displayId).isEqualTo(2)
- assertThat(actualDisplay2.width).isEqualTo(200f)
- assertThat(actualDisplay2.height).isEqualTo(600f)
- assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.offset).isEqualTo(0f)
- assertThat(actualDisplay2.children).hasSize(1)
+ verifyDisplay(actualDisplay2, id = 2, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 1)
val actualDisplay3 = actualDisplay2.children[0]
- assertThat(actualDisplay3.displayId).isEqualTo(3)
- assertThat(actualDisplay3.width).isEqualTo(600f)
- assertThat(actualDisplay3.height).isEqualTo(200f)
- assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.offset).isEqualTo(400f)
- assertThat(actualDisplay3.children).hasSize(1)
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 400f, noOfChildren = 1)
val actualDisplay4 = actualDisplay3.children[0]
- assertThat(actualDisplay4.displayId).isEqualTo(4)
- assertThat(actualDisplay4.width).isEqualTo(200f)
- assertThat(actualDisplay4.height).isEqualTo(600f)
- assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.offset).isEqualTo(-400f)
- assertThat(actualDisplay4.children).isEmpty()
+ verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = -400f, noOfChildren = 0)
}
@Test
@@ -513,7 +440,7 @@ class DisplayTopologyTest {
val nodes = rearrangeRects(
RectF(0f, 0f, 150f, 100f),
RectF(-150f, 0f, 0f, 100f),
- RectF(0f,-100f, 150f, 0f),
+ RectF(0f, -100f, 150f, 0f),
RectF(150f, 0f, 300f, 100f),
RectF(0f, 100f, 150f, 200f),
)
@@ -584,15 +511,15 @@ class DisplayTopologyTest {
@Test
fun rearrange_useLargerEdge() {
val nodes = rearrangeRects(
- //444111
- //444111
- //444111
- // 000222
- // 000222
- // 000222
- // 333
- // 333
- // 333
+ // 444111
+ // 444111
+ // 444111
+ // 000222
+ // 000222
+ // 000222
+ // 333
+ // 333
+ // 333
RectF(20f, 30f, 50f, 60f),
RectF(30f, 0f, 60f, 30f),
RectF(50f, 30f, 80f, 60f),
@@ -617,24 +544,25 @@ class DisplayTopologyTest {
@Test
fun rearrange_closeGaps() {
val nodes = rearrangeRects(
- //000
- //000 111
- //000 111
- // 111
+ // 000
+ // 000 111
+ // 000 111
+ // 111
//
- // 222
- // 222
- // 222
+ // 222
+ // 222
+ // 222
RectF(0f, 0f, 30f, 30f),
RectF(40f, 10f, 70f, 40f),
- RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for TOP/BOTTOM attach
+ RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for
+ // TOP/BOTTOM attach
)
assertPositioning(
nodes,
// In the case of corner adjacency, we prefer a left/right attachment.
Pair(POSITION_RIGHT, 10f),
- Pair(POSITION_BOTTOM, 40.5f), // TODO: fix implementation to remove this gap
+ Pair(POSITION_BOTTOM, 30f),
)
assertThat(nodes[0].children).containsExactly(nodes[1])
@@ -642,11 +570,50 @@ class DisplayTopologyTest {
assertThat(nodes[2].children).isEmpty()
}
+ @Test
+ fun copy() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ val copy = topology.copy()
+
+ assertThat(copy.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = copy.root!!
+ verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 1)
+
+ val actualDisplay3 = actualDisplay1.children[1]
+ verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+ offset = 400f, noOfChildren = 0)
+
+ val actualDisplay4 = actualDisplay2.children[0]
+ verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+ offset = 0f, noOfChildren = 0)
+ }
+
/**
* Runs the rearrange algorithm and returns the resulting tree as a list of nodes, with the
* root at index 0. The number of nodes is inferred from the number of positions passed.
*/
- private fun rearrangeRects(vararg pos : RectF) : List<DisplayTopology.TreeNode> {
+ private fun rearrangeRects(vararg pos: RectF): List<DisplayTopology.TreeNode> {
// Generates a linear sequence of nodes in order in the List from root to leaf,
// left-to-right. IDs are ascending from 0 to count - 1.
@@ -667,9 +634,20 @@ class DisplayTopologyTest {
return nodes
}
+ private fun verifyDisplay(display: DisplayTopology.TreeNode, id: Int, width: Float,
+ height: Float, @DisplayTopology.TreeNode.Position position: Int = 0,
+ offset: Float = 0f, noOfChildren: Int) {
+ assertThat(display.displayId).isEqualTo(id)
+ assertThat(display.width).isEqualTo(width)
+ assertThat(display.height).isEqualTo(height)
+ assertThat(display.position).isEqualTo(position)
+ assertThat(display.offset).isEqualTo(offset)
+ assertThat(display.children).hasSize(noOfChildren)
+ }
+
private fun assertPositioning(
- nodes : List<DisplayTopology.TreeNode>, vararg positions : Pair<Int, Float>) {
- assertThat(nodes.drop(1).map { Pair(it.position, it.offset )})
+ nodes: List<DisplayTopology.TreeNode>, vararg positions: Pair<Int, Float>) {
+ assertThat(nodes.drop(1).map { Pair(it.position, it.offset) })
.containsExactly(*positions)
.inOrder()
}
diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java
index a903ed91cb3d..335791c031b4 100644
--- a/core/tests/coretests/src/android/os/BinderProxyTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,7 +43,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
public class BinderProxyTest {
private static class CountingListener implements Binder.ProxyTransactListener {
int mStartedCount;
@@ -62,7 +63,7 @@ public class BinderProxyTest {
public final RavenwoodRule mRavenwood = new RavenwoodRule();
private Context mContext;
- private PowerManager mPowerManager;
+ private ActivityManager mActivityManager;
/**
* Setup any common data for the upcoming tests.
@@ -70,7 +71,7 @@ public class BinderProxyTest {
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
@Test
@@ -80,7 +81,7 @@ public class BinderProxyTest {
Binder.setProxyTransactListener(listener);
Binder.setProxyTransactListener(null);
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(7); // something which does a binder call
assertEquals(0, listener.mStartedCount);
assertEquals(0, listener.mEndedCount);
@@ -92,7 +93,7 @@ public class BinderProxyTest {
CountingListener listener = new CountingListener();
Binder.setProxyTransactListener(listener);
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(27); // something which does a binder call
assertEquals(1, listener.mStartedCount);
assertEquals(1, listener.mEndedCount);
@@ -112,7 +113,7 @@ public class BinderProxyTest {
});
// Check it does not throw..
- mPowerManager.isInteractive();
+ mActivityManager.isUserRunning(47); // something which does a binder call
}
private IBinder mRemoteBinder = null;
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
index 4172bffe100c..9a679d8e8a96 100644
--- a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
@@ -31,6 +31,8 @@ import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.os.SkipPresubmit;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -131,6 +133,7 @@ public class BinderThreadPriorityTest {
}
@Test
+ @SkipPresubmit("b/381950874: bitrot and failed")
public void testPassPriorityToService() throws Exception {
for (int prio = 19; prio >= -20; prio--) {
Process.setThreadPriority(prio);
@@ -146,6 +149,7 @@ public class BinderThreadPriorityTest {
}
@Test
+ @SkipPresubmit("b/381950874: bitrot and failed")
public void testCallBackFromServiceWithPriority() throws Exception {
for (int prio = -20; prio <= 19; prio++) {
final int expected = prio;
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index e8c701ef2615..2a67716aa215 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -16,8 +16,10 @@
package android.os;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -103,4 +105,99 @@ public class BuildTest {
mSetFlagsRule.disableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
assertFalse(Flags.androidOsBuildVanillaIceCream());
}
+
+ @Test
+ public void testParseFullVersionCorrectInputMajorDotMinor() throws Exception {
+ int version = Build.parseFullVersion("12.34");
+ assertEquals(12, Build.getMajorSdkVersion(version));
+ assertEquals(34, Build.getMinorSdkVersion(version));
+ }
+
+ @Test
+ public void testParseFullVersionCorrectInputOmitDotMinor() throws Exception {
+ int version = Build.parseFullVersion("1234");
+ assertEquals(1234, Build.getMajorSdkVersion(version));
+ assertEquals(0, Build.getMinorSdkVersion(version));
+ }
+
+ @Test
+ public void testParseFullVersionCorrectInputCurDevelopment() throws Exception {
+ int version = Build.parseFullVersion(Integer.toString(Build.VERSION_CODES.CUR_DEVELOPMENT));
+ assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, Build.getMajorSdkVersion(version));
+ assertEquals(0, Build.getMinorSdkVersion(version));
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputEmptyString() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputNoNumbersInString() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("foobar");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputUnexpectedPatchVersion() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("1.2.3");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputLeadingDotMissingMajorVersion() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion(".1234");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputTrailingDotMissingMinorVersion()
+ throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("1234.");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputNegativeMajorVersion() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("-12.34");
+ });
+ }
+
+ @Test
+ public void testParseFullVersionIncorrectInputNegativeMinorVersion() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.parseFullVersion("12.-34");
+ });
+ }
+
+ @Test
+ public void testFullVersionToStringCorrectInput() throws Exception {
+ assertEquals("0.0", Build.fullVersionToString(0));
+ assertEquals("1.0", Build.fullVersionToString(1 * 100000 + 0));
+ assertEquals("1.1", Build.fullVersionToString(1 * 100000 + 1));
+ assertEquals("12.34", Build.fullVersionToString(12 * 100000 + 34));
+ }
+
+ @Test
+ public void testFullVersionToStringSameStringAfterRoundTripViaParseFullVersion()
+ throws Exception {
+ String s = "12.34";
+ int major = Build.getMajorSdkVersion(Build.parseFullVersion(s));
+ int minor = Build.getMinorSdkVersion(Build.parseFullVersion(s));
+ assertEquals(s, Build.fullVersionToString(major * 100000 + minor));
+ }
+
+ @Test
+ public void testFullVersionToStringIncorrectInputNegativeVersion() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ Build.fullVersionToString(-1);
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index e14608ac503d..bb8356f7aebe 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -16,13 +16,21 @@
package android.os;
+import static android.app.Flags.FLAG_PIC_CACHE_NULLS;
+import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
import android.multiuser.Flags;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.os.IpcDataCache;
import androidx.test.filters.SmallTest;
@@ -43,6 +51,10 @@ import org.junit.Test;
@SmallTest
public class IpcDataCacheTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
// Configuration for creating caches
private static final String MODULE = IpcDataCache.MODULE_TEST;
private static final String API = "testApi";
@@ -287,7 +299,12 @@ public class IpcDataCacheTest {
@Override
public String apply(Integer qv) {
mRecomputeCount += 1;
- return "foo" + qv.toString();
+ // Special case for testing caches of nulls. Integers in the range 30-40 return null.
+ if (qv >= 30 && qv < 40) {
+ return null;
+ } else {
+ return "foo" + qv.toString();
+ }
}
int getRecomputeCount() {
@@ -406,31 +423,16 @@ public class IpcDataCacheTest {
}
@Test
- public void testConfig() {
+ public void testConfigDisable() {
+ // Create a set of caches based on a set of chained configs.
IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA");
TestCache ac = new TestCache(a);
- assertEquals(8, a.maxEntries());
- assertEquals(MODULE, a.module());
- assertEquals("apiA", a.api());
- assertEquals("apiA", a.name());
IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB");
TestCache bc = new TestCache(b);
- assertEquals(8, b.maxEntries());
- assertEquals(MODULE, b.module());
- assertEquals("apiB", b.api());
- assertEquals("apiB", b.name());
IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC");
TestCache cc = new TestCache(c);
- assertEquals(8, c.maxEntries());
- assertEquals(MODULE, c.module());
- assertEquals("apiC", c.api());
- assertEquals("nameC", c.name());
IpcDataCache.Config d = a.child("nameD");
TestCache dc = new TestCache(d);
- assertEquals(8, d.maxEntries());
- assertEquals(MODULE, d.module());
- assertEquals("apiA", d.api());
- assertEquals("nameD", d.name());
a.disableForCurrentProcess();
assertEquals(ac.isDisabled(), true);
@@ -449,6 +451,7 @@ public class IpcDataCacheTest {
assertEquals(ec.isDisabled(), true);
}
+
// Verify that invalidating the cache from an app process would fail due to lack of permissions.
@Test
@android.platform.test.annotations.DisabledOnRavenwood(
@@ -507,4 +510,47 @@ public class IpcDataCacheTest {
// Re-enable test mode (so that the cleanup for the test does not throw).
IpcDataCache.setTestMode(true);
}
+
+ @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS)
+ @Test
+ public void testCachingNulls() {
+ IpcDataCache.Config c =
+ new IpcDataCache.Config(4, IpcDataCache.MODULE_TEST, "testCachingNulls");
+ TestCache cache;
+ cache = new TestCache(c.cacheNulls(true));
+ cache.invalidateCache();
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ assertEquals(3, cache.getRecomputeCount());
+
+ cache = new TestCache(c.cacheNulls(false));
+ cache.invalidateCache();
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ // The recompute is 4 because nulls were not cached.
+ assertEquals(4, cache.getRecomputeCount());
+
+ // Verify that the default is not to cache nulls.
+ cache = new TestCache(c);
+ cache.invalidateCache();
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo1", cache.query(1));
+ assertEquals("foo2", cache.query(2));
+ assertEquals(null, cache.query(30));
+ // The recompute is 4 because nulls were not cached.
+ assertEquals(4, cache.getRecomputeCount());
+ }
}
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 549e66658b85..30f6636766d5 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -26,262 +26,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-@Suppress // Failing.
@RunWith(AndroidJUnit4.class)
public class MessageQueueTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
- private static class BaseTestHandler extends TestHandlerThread {
- Handler mHandler;
- int mLastMessage;
- int mCount;
-
- public BaseTestHandler() {
- }
-
- public void go() {
- mHandler = new Handler() {
- public void handleMessage(Message msg) {
- BaseTestHandler.this.handleMessage(msg);
- }
- };
- }
-
- public void handleMessage(Message msg) {
- if (!msg.isInUse()) {
- failure(new RuntimeException(
- "msg.isInuse is false, should always be true, #" + msg.what));
- }
- if (mCount <= mLastMessage) {
- if (msg.what != mCount) {
- failure(new RuntimeException(
- "Expected message #" + mCount
- + ", received #" + msg.what));
- } else if (mCount == mLastMessage) {
- success();
- }
- mCount++;
- } else {
- failure(new RuntimeException(
- "Message received after done, #" + msg.what));
- }
- }
- }
-
- @Test
- @MediumTest
- public void testMessageOrder() throws Exception {
- TestHandlerThread tester = new BaseTestHandler() {
- public void go() {
- super.go();
- long now = SystemClock.uptimeMillis() + 200;
- mLastMessage = 4;
- mCount = 0;
- mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1);
- mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2);
- mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2);
- mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0);
- mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0);
- }
- };
-
- tester.doTest(1000);
- }
-
- @Test
- @MediumTest
- public void testAtFrontOfQueue() throws Exception {
- TestHandlerThread tester = new BaseTestHandler() {
- public void go() {
- super.go();
- long now = SystemClock.uptimeMillis() + 200;
- mLastMessage = 3;
- mCount = 0;
- mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now);
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2));
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0));
- }
-
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- if (msg.what == 0) {
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1));
- }
- }
- };
-
- tester.doTest(1000);
- }
-
- private static class TestFieldIntegrityHandler extends TestHandlerThread {
- Handler mHandler;
- int mLastMessage;
- int mCount;
-
- public TestFieldIntegrityHandler() {
- }
-
- public void go() {
- mHandler = new Handler() {
- public void handleMessage(Message msg) {
- TestFieldIntegrityHandler.this.handleMessage(msg);
- }
- };
- }
-
- public void handleMessage(Message msg) {
- if (!msg.isInUse()) {
- failure(new RuntimeException(
- "msg.isInuse is false, should always be true, #" + msg.what));
- }
- if (mCount <= mLastMessage) {
- if (msg.what != mCount) {
- failure(new RuntimeException(
- "Expected message #" + mCount
- + ", received #" + msg.what));
- } else if (mCount == mLastMessage) {
- success();
- }
- mCount++;
- } else {
- failure(new RuntimeException(
- "Message received after done, #" + msg.what));
- }
- }
- }
-
- @Test
- @MediumTest
- public void testFieldIntegrity() throws Exception {
-
- TestHandlerThread tester = new TestFieldIntegrityHandler() {
- Bundle mBundle;
-
- public void go() {
- super.go();
- mLastMessage = 1;
- mCount = 0;
- mHandler.sendMessage(mHandler.obtainMessage(0));
- }
-
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- if (msg.what == 0) {
- msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM;
- msg.what = 1;
- msg.arg1 = 456;
- msg.arg2 = 789;
- msg.obj = this;
- msg.replyTo = null;
- mBundle = new Bundle();
- msg.data = mBundle;
- msg.data.putString("key", "value");
-
- Message newMsg = mHandler.obtainMessage();
- newMsg.copyFrom(msg);
- if (newMsg.isInUse() != false) {
- failure(new RuntimeException(
- "newMsg.isInUse is true should be false after copyFrom"));
- }
- if (newMsg.flags != 0) {
- failure(new RuntimeException(String.format(
- "newMsg.flags is %d should be 0 after copyFrom", newMsg.flags)));
- }
- if (newMsg.what != 1) {
- failure(new RuntimeException(String.format(
- "newMsg.what is %d should be %d after copyFrom", newMsg.what, 1)));
- }
- if (newMsg.arg1 != 456) {
- failure(new RuntimeException(String.format(
- "newMsg.arg1 is %d should be %d after copyFrom", msg.arg1, 456)));
- }
- if (newMsg.arg2 != 789) {
- failure(new RuntimeException(String.format(
- "newMsg.arg2 is %d should be %d after copyFrom", msg.arg2, 789)));
- }
- if (newMsg.obj != this) {
- failure(new RuntimeException(
- "newMsg.obj should be 'this' after copyFrom"));
- }
- if (newMsg.replyTo != null) {
- failure(new RuntimeException(
- "newMsg.replyTo should be null after copyFrom"));
- }
- if (newMsg.data == mBundle) {
- failure(new RuntimeException(
- "newMsg.data should NOT be mBundle after copyFrom"));
- }
- if (!newMsg.data.getString("key").equals(mBundle.getString("key"))) {
- failure(new RuntimeException(String.format(
- "newMsg.data.getString(\"key\") is %s and does not equal" +
- " mBundle.getString(\"key\") which is %s after copyFrom",
- newMsg.data.getString("key"), mBundle.getString("key"))));
- }
- if (newMsg.when != 0) {
- failure(new RuntimeException(String.format(
- "newMsg.when is %d should be 0 after copyFrom", newMsg.when)));
- }
- if (newMsg.target != mHandler) {
- failure(new RuntimeException(
- "newMsg.target is NOT mHandler after copyFrom"));
- }
- if (newMsg.callback != null) {
- failure(new RuntimeException(
- "newMsg.callback is NOT null after copyFrom"));
- }
-
- mHandler.sendMessage(newMsg);
- } else if (msg.what == 1) {
- if (msg.isInUse() != true) {
- failure(new RuntimeException(String.format(
- "msg.isInUse is false should be true after when processing %d",
- msg.what)));
- }
- if (msg.arg1 != 456) {
- failure(new RuntimeException(String.format(
- "msg.arg1 is %d should be %d when processing # %d",
- msg.arg1, 456, msg.what)));
- }
- if (msg.arg2 != 789) {
- failure(new RuntimeException(String.format(
- "msg.arg2 is %d should be %d when processing # %d",
- msg.arg2, 789, msg.what)));
- }
- if (msg.obj != this) {
- failure(new RuntimeException(String.format(
- "msg.obj should be 'this' when processing # %d", msg.what)));
- }
- if (msg.replyTo != null) {
- failure(new RuntimeException(String.format(
- "msg.replyTo should be null when processing # %d", msg.what)));
- }
- if (!msg.data.getString("key").equals(mBundle.getString("key"))) {
- failure(new RuntimeException(String.format(
- "msg.data.getString(\"key\") is %s and does not equal" +
- " mBundle.getString(\"key\") which is %s when processing # %d",
- msg.data.getString("key"), mBundle.getString("key"), msg.what)));
- }
- if (msg.when != 0) {
- failure(new RuntimeException(String.format(
- "msg.when is %d should be 0 when processing # %d",
- msg.when, msg.what)));
- }
- if (msg.target != null) {
- failure(new RuntimeException(String.format(
- "msg.target is NOT null when processing # %d", msg.what)));
- }
- if (msg.callback != null) {
- failure(new RuntimeException(String.format(
- "msg.callback is NOT null when processing # %d", msg.what)));
- }
- } else {
- failure(new RuntimeException(String.format(
- "Unexpected msg.what is %d" + msg.what)));
- }
- }
- };
-
- tester.doTest(1000);
- }
}
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index e4e965f1cadb..b8d1979e99b8 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -30,11 +30,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
@@ -54,7 +53,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+@DisabledOnRavenwood(blockedBy = PowerManager.class)
public class PowerManagerTest {
private static final String TAG = "PowerManagerTest";
@@ -83,19 +82,14 @@ public class PowerManagerTest {
String[] keys, String[] values);
static {
- if (!RavenwoodRule.isUnderRavenwood()) {
+ if (!RavenwoodRule.isOnRavenwood()) {
System.loadLibrary("powermanagertest_jni");
}
}
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
// Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
@Rule
- public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
/**
* Setup any common data for the upcoming tests.
diff --git a/core/tests/coretests/src/android/os/TestLooperManagerTest.java b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
deleted file mode 100644
index 4d64a3a94b41..000000000000
--- a/core/tests/coretests/src/android/os/TestLooperManagerTest.java
+++ /dev/null
@@ -1,91 +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.os;
-
-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 android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class TestLooperManagerTest {
- private static final String TAG = "TestLooperManagerTest";
-
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
- .setProvideMainThread(true)
- .build();
-
- @Test
- public void testMainThread() throws Exception {
- doTest(Looper.getMainLooper());
- }
-
- @Test
- public void testCustomThread() throws Exception {
- final HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- doTest(thread.getLooper());
- }
-
- private void doTest(Looper looper) throws Exception {
- final TestLooperManager tlm =
- InstrumentationRegistry.getInstrumentation().acquireLooperManager(looper);
-
- final Handler handler = new Handler(looper);
- final CountDownLatch latch = new CountDownLatch(1);
-
- assertFalse(tlm.hasMessages(handler, null, 42));
-
- handler.sendEmptyMessage(42);
- handler.post(() -> {
- latch.countDown();
- });
- assertTrue(tlm.hasMessages(handler, null, 42));
- assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
-
- final Message first = tlm.next();
- assertEquals(42, first.what);
- assertNull(first.callback);
- tlm.execute(first);
- assertFalse(tlm.hasMessages(handler, null, 42));
- assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
- tlm.recycle(first);
-
- final Message second = tlm.next();
- assertNotNull(second.callback);
- tlm.execute(second);
- assertFalse(tlm.hasMessages(handler, null, 42));
- assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
- tlm.recycle(second);
-
- tlm.release();
- }
-}
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
index 58a434a5052a..a04a6625458c 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -18,12 +18,10 @@ package android.os;
import static org.junit.Assert.assertThrows;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -34,16 +32,11 @@ import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = WorkDuration.class)
+@DisabledOnRavenwood(blockedBy = WorkDuration.class)
public class WorkDurationUnitTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
// Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
@Rule
- public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
public void setUp() {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index c3bd0657c511..108f5ba3f8a8 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -211,6 +211,102 @@ public class InsetsSourceTest {
}
@Test
+ public void testCalculateInsets_partialSideIntersection_leftCenter() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftTop() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftBottom() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topCenter() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topLeft() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topRight() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightCenter() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightTop() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightBottom() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomCenter() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomLeft() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomRight() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
public void testCalculateVisibleInsets_override() {
mSource.setFrame(new Rect(0, 0, 500, 100));
mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index ae3ad36b532c..43c404e849fe 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -213,6 +213,35 @@ public class ViewGroupTest {
assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
}
+ @Test
+ public void testMeasureCache() {
+ final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST);
+ final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
+ final Context context = getInstrumentation().getContext();
+ final View child = new View(context);
+ final TestView parent = new TestView(context, 0);
+ parent.addView(child);
+
+ child.setPadding(1, 2, 3, 4);
+ parent.measure(spec1, spec1);
+ assertEquals(4, parent.getMeasuredWidth());
+ assertEquals(6, parent.getMeasuredHeight());
+
+ child.setPadding(5, 6, 7, 8);
+ parent.measure(spec2, spec2);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+
+ // This ends the state of forceLayout.
+ parent.layout(0, 0, 50, 50);
+
+ // The cached values should be cleared after the new setPadding is called. And the measured
+ // width and height should be up-to-date.
+ parent.measure(spec1, spec1);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+ }
+
private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
final ViewParent parent = view.getParent();
@@ -240,6 +269,19 @@ public class ViewGroupTest {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// We don't layout this view.
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = 0;
+ int measuredHeight = 0;
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ measuredWidth += child.getPaddingLeft() + child.getPaddingRight();
+ measuredHeight += child.getPaddingTop() + child.getPaddingBottom();
+ }
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
}
public static class AutofillableTestView extends TestView {
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index a8fd913d857f..be65277a020e 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -69,6 +69,141 @@ public class DateTimeViewTest {
Assert.assertFalse(dateTimeView.wasLayoutRequested());
}
+ @UiThreadTest
+ @Test
+ public void disambiguationTextMask_none_noPastOrFutureDisambiguationText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeDisambiguationTextMask(0);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Days
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void disambiguationTextMask_bothPastAndFuture_usesPastAndFutureDisambiguationText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeDisambiguationTextMask(
+ DateTimeView.DISAMBIGUATION_TEXT_PAST | DateTimeView.DISAMBIGUATION_TEXT_FUTURE);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Days
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void unitDisplayLength_shortest_noMediumText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_SHORTEST);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+ // Days excluded because the string is the same for both shortest length and medium length
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+ }
+
+ @UiThreadTest
+ @Test
+ public void unitDisplayLength_medium_usesMediumText() {
+ final TestDateTimeView dateTimeView = new TestDateTimeView();
+ dateTimeView.setShowRelativeTime(true);
+ dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM);
+
+ // Minutes
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+ // Hours
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+ // Days excluded because the string is the same for both shortest length and medium length
+
+ // Years
+ dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+
+ dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+ Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+ }
+
private static class TestDateTimeView extends DateTimeView {
private boolean mRequestedLayout = false;
diff --git a/core/tests/coretests/src/android/window/OWNERS b/core/tests/coretests/src/android/window/OWNERS
index 6c80cf9e5945..b3fcea2907a1 100644
--- a/core/tests/coretests/src/android/window/OWNERS
+++ b/core/tests/coretests/src/android/window/OWNERS
@@ -1,2 +1,3 @@
include /services/core/java/com/android/server/wm/OWNERS
-charlesccchen@google.com
+
+# Bug component: 1519745 = per-file WindowContext*,WindowMetrics*,WindowProvider*,WindowTokenClient* \ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index f78bc9294357..74b4de1833ea 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -16,7 +16,6 @@
package com.android.internal.accessibility;
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@@ -64,9 +63,6 @@ import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -75,7 +71,6 @@ import android.view.Display;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Toast;
@@ -88,7 +83,6 @@ import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -106,8 +100,6 @@ import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -423,7 +415,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -446,58 +437,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
- configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
- configureValidShortcutService();
- Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
- getController().performAccessibilityShortcut();
-
- ArgumentCaptor<DialogInterface.OnClickListener> captor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
- verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
- captor.capture());
- captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
-
- assertThat(
- Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
- ).isEmpty();
- assertThat(Settings.Secure.getInt(
- mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
- AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
- throws Exception {
- configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
- turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ true);
-
- assertThat(
- Settings.Secure.getString(mContentResolver, ENABLED_ACCESSIBILITY_SERVICES)
- ).isEmpty();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
- throws Exception {
- configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
- Settings.Secure.putString(
- mContentResolver, ACCESSIBILITY_BUTTON_TARGETS, SERVICE_NAME_STRING);
-
- turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ true);
-
- assertThat(
- Settings.Secure.getString(mContentResolver, ENABLED_ACCESSIBILITY_SERVICES)
- ).isEqualTo(SERVICE_NAME_STRING);
- }
-
- @Test
public void turnOffVolumeShortcutForStandardA11yService_shouldNotTurnOffA11yService()
throws Exception {
turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ false);
@@ -530,7 +469,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -554,30 +492,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
- throws Exception {
- configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
- configureDefaultAccessibilityService();
- Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
- getController().performAccessibilityShortcut();
-
- ArgumentCaptor<DialogInterface.OnClickListener> captor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
- verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
- captor.capture());
- captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
-
- assertThat(Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
- assertThat(Settings.Secure.getInt(
- mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
- AccessibilityShortcutController.DialogStatus.SHOWN);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -601,29 +515,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
- throws Exception {
- configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
- configureDefaultAccessibilityService();
- Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
- getController().performAccessibilityShortcut();
-
- ArgumentCaptor<DialogInterface.OnClickListener> captor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
- verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
- captor.capture());
- captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
-
- assertThat(Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
- assertThat(Settings.Secure.getInt(
- mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
- AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
- }
-
- @Test
public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -638,9 +529,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @EnableFlags({
- Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS,
- Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE})
public void testOnAccessibilityShortcut_settingNull_dialogShown_enablesDefaultShortcut()
throws Exception {
configureDefaultAccessibilityService();
@@ -658,24 +546,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @EnableFlags(Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void testOnAccessibilityShortcut_settingNull_dialogShown_writesDefaultSetting()
- throws Exception {
- configureDefaultAccessibilityService();
- Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
- AccessibilityShortcutController.DialogStatus.SHOWN);
- // Setting is only `null` during SUW.
- Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null);
- getController().performAccessibilityShortcut();
-
- assertThat(Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
- verify(mAccessibilityManagerService).performAccessibilityShortcut(
- Display.DEFAULT_DISPLAY, HARDWARE, null);
- }
-
- @Test
public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
frameworkFeatureMap =
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
index 8e906fda89f0..478cef86cdba 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -25,8 +25,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.AlertDialog;
import android.content.Context;
import android.os.RemoteException;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
@@ -35,7 +33,6 @@ import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
-import android.view.accessibility.Flags;
import android.widget.TextView;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -92,19 +89,7 @@ public class AccessibilityServiceWarningTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
- public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_isSystemDialog() {
- createAccessibilityServiceWarningDialog_hasExpectedWindowParams(true);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_notSystemDialog() {
- createAccessibilityServiceWarningDialog_hasExpectedWindowParams(false);
- }
-
- private void createAccessibilityServiceWarningDialog_hasExpectedWindowParams(
- boolean expectSystemDialog) {
final AlertDialog dialog =
AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
mContext,
@@ -116,11 +101,7 @@ public class AccessibilityServiceWarningTest {
expect.that(dialogWindow.getAttributes().privateFlags
& SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- if (expectSystemDialog) {
- expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
- } else {
- expect.that(dialogWindow.getAttributes().type).isNotEqualTo(TYPE_SYSTEM_DIALOG);
- }
+ expect.that(dialogWindow.getAttributes().type).isNotEqualTo(TYPE_SYSTEM_DIALOG);
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 5339d915c8a4..acf7dbcaa5fe 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -33,12 +33,7 @@ import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -68,8 +63,6 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class InvisibleToggleAccessibilityServiceTargetTest {
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock
private IAccessibilityManager mAccessibilityManagerService;
@@ -117,7 +110,6 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
- @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
mSut.onCheckedChanged(true);
@@ -130,7 +122,6 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
- @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
mSut.onCheckedChanged(false);
verify(mAccessibilityManagerService).enableShortcutsForTargets(
@@ -140,86 +131,4 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
anyInt());
assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
}
-
- @Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
- enableA11yService(/* enable= */ true);
- addShortcutForA11yService(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ false);
- addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ true);
-
- mSut.onCheckedChanged(/* isChecked= */ true);
-
- assertA11yServiceState(/* enabled= */ true);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
- enableA11yService(/* enable= */ false);
- addShortcutForA11yService(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ false);
- addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ false);
-
- mSut.onCheckedChanged(/* isChecked= */ true);
-
- assertA11yServiceState(/* enabled= */ true);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
- enableA11yService(/* enable= */ true);
- addShortcutForA11yService(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ true);
- addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ true);
-
- mSut.onCheckedChanged(/* isChecked= */ false);
-
- assertA11yServiceState(/* enabled= */ true);
- }
-
- @Test
- @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
- public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
- enableA11yService(/* enable= */ true);
- addShortcutForA11yService(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ true);
- addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ false);
-
- mSut.onCheckedChanged(/* isChecked= */ false);
-
- assertA11yServiceState(/* enabled= */ false);
- }
-
- private void enableA11yService(boolean enable) {
- Settings.Secure.putString(
- mContextSpy.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- enable ? ALWAYS_ON_SERVICE_COMPONENT_NAME : "");
- }
-
- private void addShortcutForA11yService(String shortcutKey, boolean add) {
- Settings.Secure.putString(
- mContextSpy.getContentResolver(),
- shortcutKey,
- add ? ALWAYS_ON_SERVICE_COMPONENT_NAME : "");
- }
-
- private void assertA11yServiceState(boolean enabled) {
- if (enabled) {
- assertThat(
- Settings.Secure.getString(
- mContextSpy.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
- ).contains(ALWAYS_ON_SERVICE_COMPONENT_NAME);
- } else {
- assertThat(
- Settings.Secure.getString(
- mContextSpy.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
- ).doesNotContain(ALWAYS_ON_SERVICE_COMPONENT_NAME);
- }
- }
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index 3b9f35b1eb68..e6586b3c2583 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -496,4 +496,58 @@ public class ArrayUtilsTest {
// expected
}
}
+
+ // Note: the zeroize() tests only test the behavior that can be tested from a Java test.
+ // They do not verify that no copy of the data is left anywhere.
+
+ @Test
+ @SmallTest
+ public void testZeroizeNonMovableByteArray() {
+ final int length = 10;
+ byte[] array = ArrayUtils.newNonMovableByteArray(length);
+ assertArrayEquals(array, new byte[length]);
+ Arrays.fill(array, (byte) 0xff);
+ ArrayUtils.zeroize(array);
+ assertArrayEquals(array, new byte[length]);
+ }
+
+ @Test
+ @SmallTest
+ public void testZeroizeRegularByteArray() {
+ final int length = 10;
+ byte[] array = new byte[length];
+ assertArrayEquals(array, new byte[length]);
+ Arrays.fill(array, (byte) 0xff);
+ ArrayUtils.zeroize(array);
+ assertArrayEquals(array, new byte[length]);
+ }
+
+ @Test
+ @SmallTest
+ public void testZeroizeNonMovableCharArray() {
+ final int length = 10;
+ char[] array = ArrayUtils.newNonMovableCharArray(length);
+ assertArrayEquals(array, new char[length]);
+ Arrays.fill(array, (char) 0xff);
+ ArrayUtils.zeroize(array);
+ assertArrayEquals(array, new char[length]);
+ }
+
+ @Test
+ @SmallTest
+ public void testZeroizeRegularCharArray() {
+ final int length = 10;
+ char[] array = new char[length];
+ assertArrayEquals(array, new char[length]);
+ Arrays.fill(array, (char) 0xff);
+ ArrayUtils.zeroize(array);
+ assertArrayEquals(array, new char[length]);
+ }
+
+ @Test
+ @SmallTest
+ public void testZeroize_acceptsNull() {
+ ArrayUtils.zeroize((byte[]) null);
+ ArrayUtils.zeroize((char[]) null);
+ }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index 40710577b154..5f25e9315831 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -439,6 +439,498 @@ public class VibrationEffectXmlSerializationTest {
}
@Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffect_allSucceed() throws Exception {
+ VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+ .addControlPoint(0.2f, 80f, 10)
+ .addControlPoint(0.5f, 150f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, xml);
+ assertPublicApisRoundTrip(effect);
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, xml);
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffectWithInitialFrequency_allSucceed() throws Exception {
+ VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+ .setInitialFrequencyHz(20)
+ .addControlPoint(0.2f, 80f, 10)
+ .addControlPoint(0.5f, 150f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0">
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, xml);
+ assertPublicApisRoundTrip(effect);
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, xml);
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffect_badXml_throwsException() throws IOException {
+ // Incomplete XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10">
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ </waveform-envelope-effect>
+ """);
+
+ // Bad vibration XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """);
+
+ // "waveform-envelope-effect" tag with invalid attributes
+ assertParseElementFails("""
+ <vibration-effect>
+ <waveform-envelope-effect init_freq="20.0">
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffect_noControlPoints_allFail() throws IOException {
+ String xml = "<vibration-effect><waveform-envelope-effect/></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = "<vibration-effect><waveform-envelope-effect> \n "
+ + "</waveform-envelope-effect></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = "<vibration-effect><waveform-envelope-effect>invalid</waveform-envelope-effect"
+ + "></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0" />
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0"> \n </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0">
+ invalid
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0">
+ <control-point />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffect_badControlPointData_allFail() throws IOException {
+ String xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="-1" frequencyHz="80.0" durationMs="100" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="0" durationMs="100" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="0">
+ <control-point amplitude="0.2" frequencyHz="30" durationMs="100" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="0" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.2" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testWaveformEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+ VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+ .setInitialFrequencyHz(20)
+ .addControlPoint(0.2f, 80f, 10)
+ .addControlPoint(0.5f, 150f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <waveform-envelope-effect initialFrequencyHz="20.0">
+ <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+ <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+ </waveform-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserFails(xml);
+ assertPublicApisSerializerFails(effect);
+ assertHiddenApisParserFails(xml);
+ assertHiddenApisSerializerFails(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffect_allSucceed() throws Exception {
+ VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+ .addControlPoint(0.2f, 0.5f, 10)
+ .addControlPoint(0.0f, 1f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, xml);
+ assertPublicApisRoundTrip(effect);
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, xml);
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffectWithInitialSharpness_allSucceed() throws Exception {
+ VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+ .setInitialSharpness(0.3f)
+ .addControlPoint(0.2f, 0.5f, 10)
+ .addControlPoint(0.0f, 1f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.3">
+ <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, xml);
+ assertPublicApisRoundTrip(effect);
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, xml);
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffect_badXml_throwsException() throws IOException {
+ // Incomplete XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10">
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """);
+ assertParseElementFails("""
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ """);
+
+ // Bad vibration XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """);
+
+ // "basic-envelope-effect" tag with invalid attributes
+ assertParseElementFails("""
+ <vibration-effect>
+ <basic-envelope-effect init_sharp="20.0">
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffect_noControlPoints_allFail() throws IOException {
+ String xml = "<vibration-effect><basic-envelope-effect/></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = "<vibration-effect><basic-envelope-effect> \n "
+ + "</basic-envelope-effect></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = "<vibration-effect><basic-envelope-effect>invalid</basic-envelope-effect"
+ + "></vibration-effect>";
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.2" />
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.2"> \n </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.2">
+ invalid
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.2">
+ <control-point />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffect_badControlPointData_allFail() throws IOException {
+ String xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="-1" sharpness="0.8" durationMs="100" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="-1" durationMs="100" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="-1.0">
+ <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="2.0">
+ <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+ <control-point intensity="0.5" sharpness="0.8" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+
+ xml = """
+ <vibration-effect>
+ <basic-envelope-effect>
+ <control-point intensity="0.2" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserFails(xml);
+ assertHiddenApisParserFails(xml);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testBasicEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+ VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+ .setInitialSharpness(0.3f)
+ .addControlPoint(0.2f, 0.5f, 10)
+ .addControlPoint(0.0f, 1f, 10)
+ .build();
+
+ String xml = """
+ <vibration-effect>
+ <basic-envelope-effect initialSharpness="0.3">
+ <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+ <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+ </basic-envelope-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserFails(xml);
+ assertPublicApisSerializerFails(effect);
+
+ assertHiddenApisParserFails(xml);
+ assertHiddenApisSerializerFails(effect);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void testVendorEffect_allSucceed() throws Exception {
PersistableBundle vendorData = new PersistableBundle();
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index b4148d657b0d..29f8d199c1d1 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -1,6 +1,23 @@
// Signature format: 2.0
package com.android.internal.vibrator.persistence {
+ public class BasicControlPoint {
+ ctor public BasicControlPoint();
+ method public long getDurationMs();
+ method public float getIntensity();
+ method public float getSharpness();
+ method public void setDurationMs(long);
+ method public void setIntensity(float);
+ method public void setSharpness(float);
+ }
+
+ public class BasicEnvelopeEffect {
+ ctor public BasicEnvelopeEffect();
+ method public java.util.List<com.android.internal.vibrator.persistence.BasicControlPoint> getControlPoint();
+ method public float getInitialSharpness();
+ method public void setInitialSharpness(float);
+ }
+
public class PredefinedEffect {
ctor public PredefinedEffect();
method public com.android.internal.vibrator.persistence.PredefinedEffectName getName();
@@ -47,14 +64,18 @@ package com.android.internal.vibrator.persistence {
public class VibrationEffect {
ctor public VibrationEffect();
+ method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional();
method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
method public byte[] getVendorEffect_optional();
method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
+ method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional();
+ method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect);
method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
method public void setVendorEffect_optional(byte[]);
method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
+ method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect);
}
public class VibrationSelect {
@@ -67,6 +88,16 @@ package com.android.internal.vibrator.persistence {
enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default;
}
+ public class WaveformControlPoint {
+ ctor public WaveformControlPoint();
+ method public float getAmplitude();
+ method public long getDurationMs();
+ method public float getFrequencyHz();
+ method public void setAmplitude(float);
+ method public void setDurationMs(long);
+ method public void setFrequencyHz(float);
+ }
+
public class WaveformEffect {
ctor public WaveformEffect();
method public com.android.internal.vibrator.persistence.WaveformEffect.Repeating getRepeating();
@@ -87,6 +118,13 @@ package com.android.internal.vibrator.persistence {
method public void setDurationMs(java.math.BigInteger);
}
+ public class WaveformEnvelopeEffect {
+ ctor public WaveformEnvelopeEffect();
+ method public java.util.List<com.android.internal.vibrator.persistence.WaveformControlPoint> getControlPoint();
+ method public float getInitialFrequencyHz();
+ method public void setInitialFrequencyHz(float);
+ }
+
public class XmlParser {
ctor public XmlParser();
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index 910a9b700b5c..b4df2d187702 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -54,6 +54,12 @@
<xs:element name="primitive-effect" type="PrimitiveEffect"/>
</xs:sequence>
+ <!-- Waveform envelope effect -->
+ <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+ <!-- Basic envelope effect -->
+ <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
</xs:choice>
</xs:complexType>
@@ -180,4 +186,54 @@
</xs:restriction>
</xs:simpleType>
+ <!-- Definition of a waveform envelope effect -->
+ <xs:complexType name="WaveformEnvelopeEffect">
+ <xs:sequence>
+ <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+ type="WaveformControlPoint" />
+ </xs:sequence>
+ <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+ </xs:complexType>
+
+ <!-- Definition of a basic envelope effect -->
+ <xs:complexType name="BasicEnvelopeEffect">
+ <xs:sequence>
+ <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+ type="BasicControlPoint" />
+ </xs:sequence>
+ <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+ </xs:complexType>
+
+ <xs:complexType name="WaveformControlPoint">
+ <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+ <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+ </xs:complexType>
+
+ <xs:complexType name="BasicControlPoint">
+ <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+ </xs:complexType>
+
+ <xs:simpleType name="ControlPointFrequency">
+ <xs:restriction base="xs:float">
+ <xs:minExclusive value="0"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="PositiveLong">
+ <xs:restriction base="xs:long">
+ <xs:minExclusive value="0"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- Normalized control point unit float in [0,1] -->
+ <xs:simpleType name="NormalizedControlPointUnit">
+ <xs:restriction base="xs:float">
+ <xs:minInclusive value="0"/>
+ <xs:maxInclusive value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+
</xs:schema>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index 3c8e01605659..fba966faa9c9 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -52,6 +52,12 @@
<xs:element name="primitive-effect" type="PrimitiveEffect"/>
</xs:sequence>
+ <!-- Waveform envelope effect -->
+ <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+ <!-- Basic envelope effect -->
+ <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
</xs:choice>
</xs:complexType>
@@ -157,4 +163,54 @@
</xs:restriction>
</xs:simpleType>
+ <!-- Definition of a waveform envelope effect -->
+ <xs:complexType name="WaveformEnvelopeEffect">
+ <xs:sequence>
+ <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+ type="WaveformControlPoint" />
+ </xs:sequence>
+ <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+ </xs:complexType>
+
+ <!-- Definition of a basic envelope effect -->
+ <xs:complexType name="BasicEnvelopeEffect">
+ <xs:sequence>
+ <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+ type="BasicControlPoint" />
+ </xs:sequence>
+ <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+ </xs:complexType>
+
+ <xs:complexType name="WaveformControlPoint">
+ <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+ <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+ </xs:complexType>
+
+ <xs:complexType name="BasicControlPoint">
+ <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+ <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+ </xs:complexType>
+
+ <xs:simpleType name="ControlPointFrequency">
+ <xs:restriction base="xs:float">
+ <xs:minExclusive value="0"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="PositiveLong">
+ <xs:restriction base="xs:long">
+ <xs:minExclusive value="0"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- Normalized control point unit float in [0,1] -->
+ <xs:simpleType name="NormalizedControlPointUnit">
+ <xs:restriction base="xs:float">
+ <xs:minInclusive value="0"/>
+ <xs:maxInclusive value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+
</xs:schema>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 897fc543517e..a26f5e383586 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -514,7 +514,6 @@ applications that come with the platform
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
- <permission name="android.permission.READ_DROPBOX_DATA" />
<permission name="android.permission.READ_LOGS" />
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index 6339a8703f01..087378bef6a1 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -4,7 +4,6 @@ rule android.hidl.** android.internal.hidl.@1
# Framework-specific renames.
rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
-rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
# for modules-utils-build dependency
rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 9bf4d65e1865..2e885145819c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -34,6 +34,7 @@ import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.text.TextRunShaper;
import android.os.Build;
@@ -2141,6 +2142,14 @@ public class Paint {
* @see FontVariationAxis
*/
public boolean setFontVariationSettings(String fontVariationSettings) {
+ return setFontVariationSettings(fontVariationSettings, 0 /* wght adjust */);
+ }
+
+ /**
+ * Set font variation settings with weight adjustment
+ * @hide
+ */
+ public boolean setFontVariationSettings(String fontVariationSettings, int wghtAdjust) {
final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
&& CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
if (useFontVariationStore) {
@@ -2154,8 +2163,13 @@ public class Paint {
long builderPtr = nCreateFontVariationBuilder(axes.length);
for (int i = 0; i < axes.length; ++i) {
- nAddFontVariationToBuilder(builderPtr, axes[i].getOpenTypeTagValue(),
- axes[i].getStyleValue());
+ int tag = axes[i].getOpenTypeTagValue();
+ float value = axes[i].getStyleValue();
+ if (tag == 0x77676874 /* wght */) {
+ value = Math.clamp(value + wghtAdjust,
+ FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX);
+ }
+ nAddFontVariationToBuilder(builderPtr, tag, value);
}
nSetFontVariationOverride(mNativePaint, builderPtr);
mFontVariationSettings = fontVariationSettings;
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 073307c7a2e8..d010c525e099 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -301,10 +301,7 @@ public class Path {
*
* @param bounds Returns the computed bounds of the path's control points.
* @param exact This parameter is no longer used.
- *
- * @deprecated use computeBounds(RectF) instead
*/
- @Deprecated
@SuppressWarnings({"UnusedDeclaration"})
public void computeBounds(@NonNull RectF bounds, boolean exact) {
computeBounds(bounds);
diff --git a/keystore/java/Android.bp b/keystore/java/Android.bp
index 21edff1e1c96..264ac5ff1d92 100644
--- a/keystore/java/Android.bp
+++ b/keystore/java/Android.bp
@@ -13,5 +13,13 @@ filegroup {
"**/*.java",
"**/*.aidl",
],
+ exclude_srcs: select(release_flag("RELEASE_ATTEST_MODULES"), {
+ true: [
+ "android/security/KeyStore2HalCurrent.java",
+ ],
+ default: [
+ "android/security/KeyStore2HalLatest.java",
+ ],
+ }),
visibility: ["//frameworks/base"],
}
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index dd703f5eefb9..f5cf571ad955 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -101,7 +101,7 @@ public class KeyStore2 {
R execute(IKeystoreService service) throws RemoteException;
}
- private <R> R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest<R> request)
+ <R> R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest<R> request)
throws KeyStoreException {
IKeystoreService service = getService(false /* retryLookup */);
boolean firstTry = true;
@@ -369,6 +369,18 @@ public class KeyStore2 {
}
}
+ /**
+ * Returns tag-specific info required to interpret a tag's attested value.
+ * @see IKeystoreService#getSupplementaryAttestationInfo(Tag) for more details.
+ * @param tag
+ * @return
+ * @throws KeyStoreException
+ * @hide
+ */
+ public byte[] getSupplementaryAttestationInfo(int tag) throws KeyStoreException {
+ return KeyStore2HalVersion.getSupplementaryAttestationInfoHelper(tag, this);
+ }
+
static KeyStoreException getKeyStoreException(int errorCode, String serviceErrorMessage) {
if (errorCode > 0) {
// KeyStore layer error
diff --git a/keystore/java/android/security/KeyStore2HalCurrent.java b/keystore/java/android/security/KeyStore2HalCurrent.java
new file mode 100644
index 000000000000..f4d8fe65c995
--- /dev/null
+++ b/keystore/java/android/security/KeyStore2HalCurrent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.security;
+
+/**
+ * @hide This class is necessary to allow the version of the AIDL interface for Keystore and
+* KeyMint used in KeyStore2.java to differ by BUILD flag `RELEASE_ATTEST_MODULES`. When
+* `RELEASE_ATTEST_MODULES` is not set, this file is included, and the current HALs for Keystore
+* (V4) and KeyMint (V3) are used.
+*/
+class KeyStore2HalVersion {
+ public static byte[] getSupplementaryAttestationInfoHelper(int tag, KeyStore2 ks)
+ throws KeyStoreException {
+ return new byte[0];
+ }
+}
diff --git a/keystore/java/android/security/KeyStore2HalLatest.java b/keystore/java/android/security/KeyStore2HalLatest.java
new file mode 100644
index 000000000000..123f1c0b8f39
--- /dev/null
+++ b/keystore/java/android/security/KeyStore2HalLatest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.security;
+
+/**
+ * @hide This class is necessary to allow the version of the AIDL interface for Keystore and
+* KeyMint used in KeyStore2.java to differ by BUILD flag `RELEASE_ATTEST_MODULES`. When
+* `RELEASE_ATTEST_MODULES` is set, this file is included, and the latest HALs for Keystore (V5)
+* and KeyMint (V4) are used.
+*/
+class KeyStore2HalVersion {
+ public static byte[] getSupplementaryAttestationInfoHelper(int tag, KeyStore2 ks)
+ throws KeyStoreException {
+ return ks.handleRemoteExceptionWithRetry(
+ (service) -> service.getSupplementaryAttestationInfo(tag));
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java
index e6091c1da8a5..740ccb53a691 100644
--- a/keystore/java/android/security/keystore/KeyStoreManager.java
+++ b/keystore/java/android/security/keystore/KeyStoreManager.java
@@ -17,9 +17,11 @@
package android.security.keystore;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
+import android.hardware.security.keymint.TagType;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.keystore2.AndroidKeyStoreProvider;
@@ -32,6 +34,8 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.ByteArrayInputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.Key;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -299,6 +303,37 @@ public final class KeyStoreManager {
return Collections.emptyList();
}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {MODULE_HASH})
+ public @interface SupplementaryAttestationInfoTagEnum {}
+
+ /**
+ * When passed into getSupplementaryAttestationInfo, getSupplementaryAttestationInfo returns the
+ * DER-encoded structure corresponding to the `Modules` schema described in the KeyMint HAL's
+ * KeyCreationResult.aidl. The SHA-256 hash of this encoded structure is what's included with
+ * the tag in attestations.
+ */
+ // TODO(b/369375199): Replace with Tag.MODULE_HASH when flagging is removed.
+ public static final int MODULE_HASH = TagType.BYTES | 724;
+
+ /**
+ * Returns tag-specific data required to interpret a tag's attested value.
+ *
+ * When performing key attestation, the obtained attestation certificate contains a list of tags
+ * and their corresponding attested values. For some tags, additional information about the
+ * attested value can be queried via this API. See individual tags for specifics.
+ *
+ * @param tag tag for which info is being requested
+ * @return tag-specific info
+ * @throws KeyStoreException if the requested info is not available
+ */
+ @FlaggedApi(android.security.keystore2.Flags.FLAG_ATTEST_MODULES)
+ public @NonNull byte[] getSupplementaryAttestationInfo(
+ @SupplementaryAttestationInfoTagEnum int tag) throws KeyStoreException {
+ return mKeyStore2.getSupplementaryAttestationInfo(tag);
+ }
+
/**
* Returns a new {@link KeyDescriptor} instance in the app domain / namespace with the {@code
* alias} set to the provided value.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9ea2943bc6da..f0613cec6a0b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -398,27 +398,23 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
new TaskFragmentAnimationParams.Builder();
final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
builder.setAnimationBackgroundColor(animationBackgroundColor);
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- final int openAnimationResId =
- splitAttributes.getAnimationParams().getOpenAnimationResId();
- builder.setOpenAnimationResId(openAnimationResId);
- final int closeAnimationResId =
- splitAttributes.getAnimationParams().getCloseAnimationResId();
- builder.setCloseAnimationResId(closeAnimationResId);
- final int changeAnimationResId =
- splitAttributes.getAnimationParams().getChangeAnimationResId();
- builder.setChangeAnimationResId(changeAnimationResId);
- }
+ final int openAnimationResId =
+ splitAttributes.getAnimationParams().getOpenAnimationResId();
+ builder.setOpenAnimationResId(openAnimationResId);
+ final int closeAnimationResId =
+ splitAttributes.getAnimationParams().getCloseAnimationResId();
+ builder.setCloseAnimationResId(closeAnimationResId);
+ final int changeAnimationResId =
+ splitAttributes.getAnimationParams().getChangeAnimationResId();
+ builder.setChangeAnimationResId(changeAnimationResId);
return builder.build();
}
@ColorInt
private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
- AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- animationBackground = splitAttributes.getAnimationParams().getAnimationBackground();
- }
+ final AnimationBackground animationBackground =
+ splitAttributes.getAnimationParams().getAnimationBackground();
if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
animationBackgroundColor = colorBackground.getColor();
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt
new file mode 100644
index 000000000000..ef8e71c2590b
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt
@@ -0,0 +1,49 @@
+/*
+ * 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
+
+import com.android.wm.shell.common.ShellExecutor
+
+/**
+ * Simple implementation of [ShellExecutor] that collects all runnables and executes them
+ * sequentially once [flushAll] is called
+ */
+class TestShellExecutor : ShellExecutor {
+
+ private val runnables: MutableList<Runnable> = mutableListOf()
+
+ override fun execute(runnable: Runnable) {
+ runnables.add(runnable)
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ execute(runnable)
+ }
+
+ override fun removeCallbacks(runnable: Runnable?) {}
+
+ override fun hasCallback(runnable: Runnable?): Boolean = false
+
+ /**
+ * Execute all posted runnables sequentially
+ */
+ fun flushAll() {
+ while (runnables.isNotEmpty()) {
+ runnables.removeAt(0).run()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index f535fbd653c5..2b4e5417f188 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -34,6 +34,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.Flags
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.WindowManagerShellWrapper
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
@@ -41,7 +42,6 @@ import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -84,16 +84,16 @@ class BubbleControllerBubbleBarTest {
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var bubblePositioner: BubblePositioner
private lateinit var bubbleData: BubbleData
- private lateinit var mainExecutor: TestExecutor
- private lateinit var bgExecutor: TestExecutor
+ private lateinit var mainExecutor: TestShellExecutor
+ private lateinit var bgExecutor: TestShellExecutor
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
ProtoLog.init()
- mainExecutor = TestExecutor()
- bgExecutor = TestExecutor()
+ mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
uiEventLoggerFake = UiEventLoggerFake()
val bubbleLogger = BubbleLogger(uiEventLoggerFake)
@@ -232,8 +232,8 @@ class BubbleControllerBubbleBarTest {
bubbleData: BubbleData,
bubbleLogger: BubbleLogger,
bubblePositioner: BubblePositioner,
- mainExecutor: TestExecutor,
- bgExecutor: TestExecutor,
+ mainExecutor: TestShellExecutor,
+ bgExecutor: TestShellExecutor,
): BubbleController {
val shellCommandHandler = ShellCommandHandler()
val shellController =
@@ -289,29 +289,6 @@ class BubbleControllerBubbleBarTest {
)
}
- private class TestExecutor : ShellExecutor {
-
- private val runnables: MutableList<Runnable> = mutableListOf()
-
- override fun execute(runnable: Runnable) {
- runnables.add(runnable)
- }
-
- override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
- execute(runnable)
- }
-
- override fun removeCallbacks(runnable: Runnable?) {}
-
- override fun hasCallback(runnable: Runnable?): Boolean = false
-
- fun flushAll() {
- while (runnables.isNotEmpty()) {
- runnables.removeAt(0).run()
- }
- }
- }
-
private class FakeBubblesStateListener : Bubbles.BubbleStateListener {
override fun onBubbleStateChange(update: BubbleBarUpdate?) {}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index b38d00da6dfa..1d0c5057c77f 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -602,8 +602,72 @@ class BubblePositionerTest {
testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
}
+ @Test
+ fun getExpandedViewContainerPadding_largeScreen_fitsMaxViewWidth() {
+ val expandedViewWidth = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_width
+ )
+ // set the screen size so that it is wide enough to fit the maximum width size
+ val screenWidth = expandedViewWidth * 2
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, screenWidth, 2000),
+ isLargeScreen = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ val padding = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding
+ )
+ val right = screenWidth - expandedViewWidth - padding
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, right, 0))
+ }
+
+ @Test
+ fun getExpandedViewContainerPadding_largeScreen_doesNotFitMaxViewWidth() {
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, 600, 2000),
+ isLargeScreen = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ val padding = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding
+ )
+ // the screen is not wide enough to fit the maximum width size, so the view fills the screen
+ // minus left and right padding
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0))
+ }
+
+ @Test
+ fun getExpandedViewContainerPadding_smallTablet() {
+ val screenWidth = 500
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, screenWidth, 2000),
+ isLargeScreen = true,
+ isSmallTablet = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ // for small tablets, the view width is set to be 0.72 * screen width
+ val viewWidth = (screenWidth * 0.72).toInt()
+ val padding = (screenWidth - viewWidth) / 2
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0))
+ }
+
private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
- positioner.setShowingInBubbleBar(true)
+ positioner.isShowingInBubbleBar = true
val windowBounds = Rect(0, 0, 2000, 2600)
val insets = Insets.of(10, 20, 5, 15)
val deviceConfig =
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 5f42bb161204..239acd37f286 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -31,20 +31,16 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation
-import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.After
@@ -52,6 +48,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
@@ -71,7 +68,7 @@ class BubbleStackViewTest {
private lateinit var iconFactory: BubbleIconFactory
private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
private lateinit var bubbleStackView: BubbleStackView
- private lateinit var shellExecutor: ShellExecutor
+ private lateinit var shellExecutor: TestShellExecutor
private lateinit var windowManager: WindowManager
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
@@ -108,7 +105,7 @@ class BubbleStackViewTest {
)
bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
- bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
+ bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
bubbleStackView =
BubbleStackView(
context,
@@ -168,6 +165,7 @@ class BubbleStackViewTest {
// This will eventually propagate an update back to the stack view, but setting the
// entire pipeline is outside the scope of a unit test.
assertThat(bubbleData.isExpanded).isTrue()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -206,6 +204,7 @@ class BubbleStackViewTest {
bubbleStackView.setSelectedBubble(bubble2)
bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -223,6 +222,7 @@ class BubbleStackViewTest {
// tap on bubble1 to select it
InstrumentationRegistry.getInstrumentation().runOnMainSync {
bubble1.iconView!!.performClick()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
@@ -233,6 +233,7 @@ class BubbleStackViewTest {
// listener wired up.
bubbleStackView.setSelectedBubble(bubble1)
bubble1.iconView!!.performClick()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -355,7 +356,7 @@ class BubbleStackViewTest {
@Test
fun removeFromWindow_stopMonitoringSwipeUpGesture() {
- spyOn(bubbleStackView)
+ bubbleStackView = Mockito.spy(bubbleStackView)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
// No way to add to window in the test environment right now so just pretend
bubbleStackView.onDetachedFromWindow()
@@ -426,55 +427,4 @@ class BubbleStackViewTest {
override fun hideCurrentInputMethod() {}
}
-
- private class TestShellExecutor : ShellExecutor {
-
- override fun execute(runnable: Runnable) {
- runnable.run()
- }
-
- override fun executeDelayed(r: Runnable, delayMillis: Long) {
- r.run()
- }
-
- override fun removeCallbacks(r: Runnable?) {}
-
- override fun hasCallback(r: Runnable): Boolean = false
- }
-
- private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
- override fun create(): BubbleTaskView {
- val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
- return BubbleTaskView(taskView, shellExecutor)
- }
- }
-
- private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
-
- override val overflowBubbles: List<Bubble>
- get() = emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {}
-
- override fun collapseStack() {}
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {}
-
- override fun removeBubble(key: String, reason: Int) {}
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {}
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
-
- override fun isStackExpanded(): Boolean = false
-
- override fun isShowingAsBubbleBar(): Boolean = false
-
- override fun hideCurrentInputMethod() {}
-
- override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
- }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index 776ea9226b06..680d015dfd2f 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -35,13 +35,13 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.WindowManagerShellWrapper
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.shared.TransactionPool
@@ -70,8 +70,8 @@ class BubbleViewInfoTaskTest {
private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
private lateinit var iconFactory: BubbleIconFactory
private lateinit var bubbleController: BubbleController
- private lateinit var mainExecutor: TestExecutor
- private lateinit var bgExecutor: TestExecutor
+ private lateinit var mainExecutor: TestShellExecutor
+ private lateinit var bgExecutor: TestShellExecutor
private lateinit var bubbleStackView: BubbleStackView
private lateinit var bubblePositioner: BubblePositioner
private lateinit var bubbleLogger: BubbleLogger
@@ -94,8 +94,8 @@ class BubbleViewInfoTaskTest {
context.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width)
)
- mainExecutor = TestExecutor()
- bgExecutor = TestExecutor()
+ mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
@@ -335,27 +335,4 @@ class BubbleViewInfoTaskTest {
bgExecutor
)
}
-
- private class TestExecutor : ShellExecutor {
-
- private val runnables: MutableList<Runnable> = mutableListOf()
-
- override fun execute(runnable: Runnable) {
- runnables.add(runnable)
- }
-
- override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
- execute(runnable)
- }
-
- override fun removeCallbacks(runnable: Runnable?) {}
-
- override fun hasCallback(runnable: Runnable?): Boolean = false
-
- fun flushAll() {
- while (runnables.isNotEmpty()) {
- runnables.removeAt(0).run()
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt
new file mode 100644
index 000000000000..3c013d3636e8
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.bubbles
+
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import java.util.Collections
+
+/** Fake implementation of [BubbleExpandedViewManager] for testing. */
+class FakeBubbleExpandedViewManager(var bubbleBar: Boolean = false, var expanded: Boolean = false) :
+ BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = Collections.emptyList()
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {}
+
+ override fun collapseStack() {}
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {}
+
+ override fun removeBubble(key: String, reason: Int) {}
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {}
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+
+ override fun isStackExpanded(): Boolean {
+ return expanded
+ }
+
+ override fun isShowingAsBubbleBar(): Boolean {
+ return bubbleBar
+ }
+
+ override fun hideCurrentInputMethod() {}
+
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
index 3279d561d4f1..bcaa63bfad36 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -19,6 +19,10 @@ package com.android.wm.shell.bubbles
import android.content.Context
import android.content.pm.ShortcutInfo
import android.content.res.Resources
+import android.view.LayoutInflater
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.wm.shell.R
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
import com.google.common.util.concurrent.MoreExecutors.directExecutor
@@ -27,6 +31,33 @@ import com.google.common.util.concurrent.MoreExecutors.directExecutor
class FakeBubbleFactory {
companion object {
+ fun createExpandedView(
+ context: Context,
+ bubblePositioner: BubblePositioner,
+ expandedViewManager: BubbleExpandedViewManager,
+ bubbleTaskView: BubbleTaskView,
+ mainExecutor: TestShellExecutor,
+ bgExecutor: TestShellExecutor,
+ bubbleLogger: BubbleLogger = BubbleLogger(UiEventLoggerFake()),
+ ): BubbleBarExpandedView {
+ val bubbleBarExpandedView =
+ (LayoutInflater.from(context)
+ .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */)
+ as BubbleBarExpandedView)
+ .apply {
+ initialize(
+ expandedViewManager,
+ bubblePositioner,
+ bubbleLogger,
+ false, /* isOverflow */
+ bubbleTaskView,
+ mainExecutor,
+ bgExecutor,
+ null, /* regionSamplingProvider */
+ )
+ }
+ return bubbleBarExpandedView
+ }
fun createViewInfo(bubbleExpandedView: BubbleBarExpandedView): BubbleViewInfo {
return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
new file mode 100644
index 000000000000..42b66aa29bfc
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.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.wm.shell.bubbles
+
+import android.app.ActivityManager
+import android.content.Context
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Implementation of [BubbleTaskViewFactory] for testing.
+ */
+class FakeBubbleTaskViewFactory(
+ private val context: Context,
+ private val mainExecutor: ShellExecutor,
+) : BubbleTaskViewFactory {
+ override fun create(): BubbleTaskView {
+ val taskViewTaskController = mock<TaskViewTaskController>()
+ val taskView = TaskView(context, taskViewTaskController)
+ val taskInfo = mock<ActivityManager.RunningTaskInfo>()
+ whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
+ return BubbleTaskView(taskView, mainExecutor)
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 1bf6af8d1f6d..bfc798bb9c79 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -33,32 +33,30 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
-import com.android.wm.shell.bubbles.BubbleData
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.BubbleTaskViewFactory
import com.android.wm.shell.bubbles.DeviceConfig
+import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
-import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import java.util.Collections
-import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import java.util.concurrent.Executor
/** Tests for [BubbleBarExpandedViewTest] */
@SmallTest
@@ -72,8 +70,8 @@ class BubbleBarExpandedViewTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private val windowManager = context.getSystemService(WindowManager::class.java)
- private lateinit var mainExecutor: TestExecutor
- private lateinit var bgExecutor: TestExecutor
+ private lateinit var mainExecutor: TestShellExecutor
+ private lateinit var bgExecutor: TestShellExecutor
private lateinit var expandedViewManager: BubbleExpandedViewManager
private lateinit var positioner: BubblePositioner
@@ -90,8 +88,8 @@ class BubbleBarExpandedViewTest {
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
ProtoLog.init()
- mainExecutor = TestExecutor()
- bgExecutor = TestExecutor()
+ mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
positioner = BubblePositioner(context, windowManager)
positioner.setShowingInBubbleBar(true)
val deviceConfig =
@@ -105,7 +103,7 @@ class BubbleBarExpandedViewTest {
)
positioner.update(deviceConfig)
- expandedViewManager = createExpandedViewManager()
+ expandedViewManager = FakeBubbleExpandedViewManager(bubbleBar = true, expanded = true)
bubbleTaskView = FakeBubbleTaskViewFactory().create()
val inflater = LayoutInflater.from(context)
@@ -426,63 +424,4 @@ class BubbleBarExpandedViewTest {
setWindowInvisible = false
}
}
-
- private fun createExpandedViewManager(): BubbleExpandedViewManager {
- return object : BubbleExpandedViewManager {
- override val overflowBubbles: List<Bubble>
- get() = Collections.emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {
- }
-
- override fun collapseStack() {
- }
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {
- }
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {
- }
-
- override fun removeBubble(key: String, reason: Int) {
- }
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {
- }
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {
- }
-
- override fun isStackExpanded(): Boolean {
- return true
- }
-
- override fun isShowingAsBubbleBar(): Boolean {
- return true
- }
-
- override fun hideCurrentInputMethod() {
- }
-
- override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
- }
- }
- }
-
- private class TestExecutor : ShellExecutor {
-
- private val runnables: MutableList<Runnable> = mutableListOf()
-
- override fun execute(runnable: Runnable) {
- runnables.add(runnable)
- }
-
- override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
- execute(runnable)
- }
-
- override fun removeCallbacks(runnable: Runnable?) {}
-
- override fun hasCallback(runnable: Runnable?): Boolean = false
- }
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 7280f8aa07a6..04c9ffbac287 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -16,14 +16,12 @@
package com.android.wm.shell.bubbles.bar
-import android.app.ActivityManager
import android.content.Context
import android.content.pm.LauncherApps
import android.graphics.PointF
import android.os.Handler
import android.os.UserManager
import android.view.IWindowManager
-import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
@@ -37,19 +35,19 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.WindowManagerShellWrapper
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.bubbles.BubbleData
import com.android.wm.shell.bubbles.BubbleDataRepository
import com.android.wm.shell.bubbles.BubbleEducationController
-import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.BubbleTaskView
-import com.android.wm.shell.bubbles.BubbleTaskViewFactory
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
+import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.bubbles.properties.BubbleProperties
@@ -57,7 +55,6 @@ import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.shared.TransactionPool
@@ -66,20 +63,16 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.After
-import java.util.Collections
import org.junit.Before
import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
/** Tests for [BubbleBarLayerView] */
@SmallTest
@@ -87,8 +80,7 @@ import org.mockito.kotlin.whenever
class BubbleBarLayerViewTest {
companion object {
- @JvmField @ClassRule
- val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+ @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
}
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -112,8 +104,8 @@ class BubbleBarLayerViewTest {
uiEventLoggerFake = UiEventLoggerFake()
val bubbleLogger = BubbleLogger(uiEventLoggerFake)
- val mainExecutor = TestExecutor()
- val bgExecutor = TestExecutor()
+ val mainExecutor = TestShellExecutor()
+ val bgExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
@@ -145,24 +137,18 @@ class BubbleBarLayerViewTest {
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger)
- val expandedViewManager = createExpandedViewManager()
- val bubbleTaskView = FakeBubbleTaskViewFactory(mainExecutor).create()
+ val expandedViewManager = FakeBubbleExpandedViewManager(bubbleBar = true, expanded = true)
+ val bubbleTaskView = FakeBubbleTaskViewFactory(context, mainExecutor).create()
val bubbleBarExpandedView =
- (LayoutInflater.from(context)
- .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */)
- as BubbleBarExpandedView)
- .apply {
- initialize(
- expandedViewManager,
- bubblePositioner,
- bubbleLogger,
- false /* isOverflow */,
- bubbleTaskView,
- mainExecutor,
- bgExecutor,
- null, /* regionSamplingProvider */
- )
- }
+ FakeBubbleFactory.createExpandedView(
+ context,
+ bubblePositioner,
+ expandedViewManager,
+ bubbleTaskView,
+ mainExecutor,
+ bgExecutor,
+ bubbleLogger,
+ )
val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
bubble = FakeBubbleFactory.createChatBubble(context, viewInfo = viewInfo)
@@ -179,8 +165,8 @@ class BubbleBarLayerViewTest {
windowManager: WindowManager?,
bubbleLogger: BubbleLogger,
bubblePositioner: BubblePositioner,
- mainExecutor: TestExecutor,
- bgExecutor: TestExecutor,
+ mainExecutor: TestShellExecutor,
+ bgExecutor: TestShellExecutor,
): BubbleController {
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
@@ -310,74 +296,9 @@ class BubbleBarLayerViewTest {
getInstrumentation().waitForIdleSync()
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) }
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
- AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
- }
-
- private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) :
- BubbleTaskViewFactory {
- override fun create(): BubbleTaskView {
- val taskViewTaskController = mock<TaskViewTaskController>()
- val taskView = TaskView(context, taskViewTaskController)
- val taskInfo = mock<ActivityManager.RunningTaskInfo>()
- whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
- return BubbleTaskView(taskView, mainExecutor)
- }
- }
-
- private fun createExpandedViewManager(): BubbleExpandedViewManager {
- return object : BubbleExpandedViewManager {
- override val overflowBubbles: List<Bubble>
- get() = Collections.emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {}
-
- override fun collapseStack() {}
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {}
-
- override fun removeBubble(key: String, reason: Int) {}
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {}
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
-
- override fun isStackExpanded(): Boolean {
- return true
- }
-
- override fun isShowingAsBubbleBar(): Boolean {
- return true
- }
-
- override fun hideCurrentInputMethod() {}
-
- override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
- }
- }
-
- private class TestExecutor : ShellExecutor {
-
- private val runnables: MutableList<Runnable> = mutableListOf()
-
- override fun execute(runnable: Runnable) {
- runnables.add(runnable)
- }
-
- override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
- execute(runnable)
- }
-
- override fun removeCallbacks(runnable: Runnable?) {}
-
- override fun hasCallback(runnable: Runnable?): Boolean = false
-
- fun flushAll() {
- while (runnables.isNotEmpty()) {
- runnables.removeAt(0).run()
- }
- }
+ AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.SCALE_Y,
+ )
}
private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) {
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
index 3dbf7542ac6e..fcf74e3c1936 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
@@ -46,15 +46,19 @@
<TextView
android:id="@+id/application_name"
android:layout_width="0dp"
- android:layout_height="20dp"
- android:maxWidth="86dp"
+ android:layout_height="wrap_content"
+ android:maxWidth="130dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
- android:lineHeight="20dp"
+ android:lineHeight="20sp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginStart="8dp"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:requiresFadingEdge="horizontal"
+ android:fadingEdgeLength="28dp"
android:clickable="false"
android:focusable="false"
tools:text="Gmail"/>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 9a1a3da06a77..07cc0e769a59 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -102,7 +102,7 @@
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Keer enige tyd terug na volskerm vanaf die appkieslys"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n app om dit te herposisioneer"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Verander grootte"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Meesleurend"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Stel terug"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 9d22fef66636..a6921b992234 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"መጠን ቀይር"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"አስማጭ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ወደነበረበት መልስ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 46ab090e310e..b72d25519e4f 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغيير الحجم"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"مجسَّم"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"استعادة"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 1e35d6ed132f..632d1265a1e6 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"আকাৰ সলনি কৰক"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্‌টো আনিব নোৱাৰি"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমাৰ্ছিভ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"পুনঃস্থাপন কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 136d4c18c3d5..cf9f1b251af7 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ölçüsünü dəyişin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"İmmersiv"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Bərpa edin"</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 10a33bb6aca7..c2d4d8b0613e 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promeni veličinu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imerzivne"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 163fbddbc967..dde2374ea491 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змяніць памер"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"З эфектам прысутнасці"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Аднавіць"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index d7da3aef02bb..7e804843dfce 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Преоразмеряване"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Възстановяване"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 9c2fc6e98818..4c6e6c1fee2f 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ছোট বড় করুন"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমারসিভ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ফিরিয়ে আনুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 911285d060f1..244149b855f6 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -87,7 +87,7 @@
<string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljajte oblačićima u svakom trenutku"</string>
<string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da upravljate time koje aplikacije i razgovori mogu imati oblačić"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string>
- <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string>
+ <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string>
<string name="bubble_shortcut_label" msgid="666269077944378311">"Oblačići"</string>
<string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Prikaz oblačića"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uvjerljivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vraćanje"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 4249373e0a3d..786ed769e7b7 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Canvia la mida"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiu"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaura"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index a12534372135..99e9a8350822 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Změnit velikost"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 5b657f4c9bb6..6021a96e8cbe 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -72,9 +72,9 @@
<string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"udvid <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
- <string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
+ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Luk boble"</string>
<string name="bubble_fullscreen_text" msgid="1006758103218086231">"Flyt til fuld skærm"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
+ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtale i boble"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat ved hjælp af bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Styr bobler når som helst"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Tilpas størrelse"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Opslugende"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gendan"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 6d360e8e0af2..7b296620099b 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Größe ändern"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Wiederherstellen"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 85a44f6d760d..879347adf406 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Αλλαγή μεγέθους"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Καθηλωτικό"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Επαναφορά"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 3e30ff048c6d..358e31476242 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 0d7189bd16b3..923f30b9a5ba 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 3e30ff048c6d..358e31476242 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 3e30ff048c6d..358e31476242 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 6a1a2e5a4d39..7a2e8cffffcf 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar el tamaño"</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>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index f93cf5a2fefd..2a30bfbd1ba1 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover 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">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f0d1d4e60392..9a15f90ac27e 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Suuruse muutmine"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Kaasahaarav"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Taasta"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index c6a7f2eca877..7c03b24eaef8 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Aldatu tamaina"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Murgiltzailea"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Leheneratu"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index d10a02d75c18..eb50ba7c9477 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغییر اندازه"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمی‌توان به اینجا منتقل کرد"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"فراگیر"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بازیابی"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 0655f9a390a0..d89e36aad3d3 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Muuta kokoa"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiivinen"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Palauta"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index b9bdbd73aed6..e2730d422013 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -74,7 +74,7 @@
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
<string name="bubble_fullscreen_text" msgid="1006758103218086231">"Passez en plein écran"</string>
- <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
+ <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index a1eb028a4f9c..a97a48cdcd46 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 22a7f7fcb35b..445cc70d4e8d 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 06c21b46a97e..6bef1bb6e061 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"કદ બદલો"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ઇમર્સિવ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"રિસ્ટોર કરો"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 0eab10c34606..95b3fc0fafd5 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,7 +84,7 @@
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string>
<string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल्स का इस्तेमाल करके चैट करें"</string>
<string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नई बातचीत, आपकी स्क्रीन पर सबसे नीचे आइकॉन के तौर पर दिखती हैं. किसी आइकॉन को बड़ा करने के लिए उस पर टैप करें या खारिज करने के लिए उसे खींचें और छोड़ें."</string>
- <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स की सुविधा को कंट्रोल करें"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स को कंट्रोल करें"</string>
<string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"किसी ऐप्लिकेशन और बातचीत के लिए बबल की सुविधा को मैनेज करने के लिए यहां टैप करें"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"साइज़ बदलें"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"वापस लाएं"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index bf756f63395b..28bab79042a0 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index b02be18cb6f2..1afb57d8c80a 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Átméretezés"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Magával ragadó"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Visszaállítás"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 59a95f0c1393..7266942434c0 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Փոխել չափը"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Ներկայության էֆեկտով"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Վերականգնել"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index baa1d0ec6bae..1197413553db 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah ukuran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersif"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index c3ad5d66e17a..9646cb375f2f 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Breyta stærð"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index b75c041afdab..c3f6b3b49d9f 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -79,12 +79,12 @@
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Le nuove conversazioni vengono mostrate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlla le bolle quando vuoi"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tocca Gestisci per disattivare le bolle dall\'app"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string>
<string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta utilizzando le bolle"</string>
<string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Le nuove conversazioni vengono visualizzate sotto forma di icone in un angolo inferiore dello schermo. Tocca per espanderle o trascina per chiuderle."</string>
- <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlla le bolle quando vuoi"</string>
<string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ridimensiona"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 5f37590298de..6f18eda13caf 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"שינוי הגודל"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"סוחף"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"שחזור"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 2960a19cef0a..c955ecb4f508 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -80,7 +80,7 @@
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"バブルはいつでも管理可能"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"このアプリからのバブルを OFF にするには、[管理] をタップしてください"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
- <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string>
+ <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近のバブルはありません"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近表示されたバブルや閉じたバブルが、ここに表示されます"</string>
<string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"チャットでバブルを使う"</string>
<string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新しい会話がアイコンとして画面下部に表示されます。タップすると開き、ドラッグして閉じることができます。"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"サイズ変更"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"没入モード"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"復元"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 6420bf531f24..2c286d2644df 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ზომის შეცვლა"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"იმერსიული"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"აღდგენა"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index ef169537a899..58afb7fdd6c4 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлшемін өзгерту"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Әсерлі"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Қалпына келтіру"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index a625201261b1..6abb66dc9ade 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ប្ដូរ​ទំហំ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ជក់ចិត្ត"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ស្ដារ"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index b2bf3a50d505..1da093d666bb 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ಇಮ್ಮರ್ಸಿವ್"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index ad0368a57460..22f2e0632b46 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"크기 조절"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"몰입형"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"복원"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 0b4eb934ff99..86529a292cff 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлчөмүн өзгөртүү"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Сүңгүтүүчү"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Калыбына келтирүү"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 9710e69a0418..fab0cb245cfd 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ປັບຂະໜາດ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ສົມຈິງ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ກູ້ຄືນ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 5bfb8e34fd53..d036e35e9cbf 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Pakeisti dydį"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 07342000ca66..dc1f7b04c21a 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Mainīt lielumu"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Iekļaujoši"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atjaunot"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 2c4503c23e80..3da196b52984 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени ја големината"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 7e20ee1fc36b..c2e747c590d8 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്‌ക്രീൻ വലുതാക്കുക"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"വലുപ്പം മാറ്റുക"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ഇമേഴ്‌സീവ്"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"പുനഃസ്ഥാപിക്കുക"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index ef24222cdef1..045fc2101481 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Хэмжээг өөрчлөх"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Бодит мэт"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Сэргээх"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index e665639ca217..01398d5c4d3a 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदला"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अ‍ॅप इथे हलवू शकत नाही"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव्ह"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोअर करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 5de79c2c9afc..3d687dcbd800 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah saiz"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index e6d355379f9a..08a935f75355 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"အရွယ်ပြင်ရန်"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"သုံးဘက်မြင်"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ပြန်ပြောင်းရန်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index bde7ec67d0cb..196507866aaf 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Endre størrelse"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Oppslukende"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gjenopprett"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index a40e3adede16..10e933245e60 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदल्नुहोस्"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिभ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोर गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index b28b69080051..fc8451522f21 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,7 +84,7 @@
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string>
<string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatten met bubbels"</string>
<string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nieuwe gesprekken verschijnen als iconen in een benedenhoek van je scherm. Tik om ze uit te vouwen of sleep om ze te sluiten."</string>
- <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string>
+ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren"</string>
<string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Formaat aanpassen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersief"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Herstellen"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 842e3def41f1..be01593fda39 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ଇମର୍ସିଭ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index e1c804a56289..fb4c83e352f7 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ਆਕਾਰ ਬਦਲੋ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ਇਮਰਸਿਵ"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index e82916b89033..fa0e7c318f7e 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmień rozmiar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 6d8c9ce2a72f..d9e5f8c77897 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index e0e91e715ed0..28dc7b0d228e 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 6d8c9ce2a72f..d9e5f8c77897 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 5a381556207e..b63a8b3b05df 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionează"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Captivant"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restabilește"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index d26eb532be28..709e90eb7fd9 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Изменить размер"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Погружение"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Восстановить"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 5bfa4c91bc6c..da1aa9d71c15 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ප්‍රතිප්‍රමාණය කරන්න"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ගිලෙන සුළු"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ප්‍රතිසාධනය කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index cd20df5e1a1c..aa7799723993 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmeniť veľkosť"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pútavé"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnoviť"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 063e47c80d5f..55452bd0e854 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Spremeni velikost"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovi"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index f2fb7da44807..0492b2f9a51f 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <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>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Përfshirës"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restauro"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index f74e460226b6..af8ac6898e83 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени величину"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Имерзивне"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 7a5549eb5f59..0c3c18c70040 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ändra storlek"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uppslukande"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Återställ"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 128ba74fd80c..4f0a6ac93b55 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Badilisha ukubwa"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Shirikishi"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Rejesha"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 668efd5ebf82..5fca404d5614 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"அளவை மாற்று"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ஈடுபட வைக்கும்"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"மீட்டெடுக்கும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index a3827bb3703a..9e0f107f7e55 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"సైజ్ మార్చండి"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్‌ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"లీనమయ్యే"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"రీస్టోర్ చేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index fe7556156e78..7be7373e03a9 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ปรับขนาด"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"สมจริง"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"คืนค่า"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index d9fb13bb0b6f..22b0174c0252 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"I-resize"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index e6d900af0cf8..79d64ba1f117 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Yeniden boyutlandır"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Etkileyici"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Geri yükle"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index ea599952a0e7..aeba9824d3f4 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змінити розмір"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалістичність"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Відновити"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 6749629f2c3e..cf6fb8926f55 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"سائز تبدیل کریں"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"عمیق"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بحال کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 3417fef84de5..c64b84373b17 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Oʻlchamini oʻzgartirish"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Tiklash"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 559bff8711bf..2a7dae4cbaef 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Đổi kích thước"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Hiển thị tối đa"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Khôi phục"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index f327653bd5d9..e45fbba6e196 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"调整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸式"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"恢复"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index e1ecd44eb69f..d5e106394720 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -125,7 +125,7 @@
<string name="select_text" msgid="5139083974039906583">"選取"</string>
<string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
<string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string>
- <string name="open_in_app_text" msgid="2874590745116268525">"在應用程式中開啟"</string>
+ <string name="open_in_app_text" msgid="2874590745116268525">"喺應用程式入面打開"</string>
<string name="new_window_text" msgid="6318648868380652280">"新視窗"</string>
<string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string>
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string>
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"身歷其境"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 1b8f704cef29..a0357e12b722 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 604031733ed2..810b6c82e09d 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -133,8 +133,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
<string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string>
- <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Shintsha usayizi"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Okugxilile"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Buyisela"</string>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 21ec84d9bc0a..9e2d23b41556 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -266,6 +266,8 @@
<dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
<!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
<dimen name="bubble_bar_expanded_view_width">412dp</dimen>
+ <!-- Offset of the expanded view when it starts sliding in as part of the switch animation -->
+ <dimen name="bubble_bar_expanded_view_switch_offset">48dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
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 8f7a2e5a6789..01d2201a5a0c 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
@@ -20,29 +20,6 @@ package com.android.wm.shell.shared;
* General shell-related constants that are shared with users of the library.
*/
public class ShellSharedConstants {
- // See IPip.aidl
- public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
- // See IBubbles.aidl
- public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
- // See ISplitScreen.aidl
- public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
- // See IOneHanded.aidl
- public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
- // See IShellTransitions.aidl
- public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
- "extra_shell_shell_transitions";
- // See IStartingWindow.aidl
- public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
- "extra_shell_starting_window";
- // See IRecentTasks.aidl
- public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
- // See IBackAnimation.aidl
- public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IDesktopMode.aidl
- public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
- // See IDragAndDrop.aidl
- public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
- // See IRecentsAnimationController.aidl
public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION =
"extra_shell_can_hand_off_animation";
}
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 04c17e54d11f..a5205ee24d05 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
@@ -162,6 +162,21 @@ public class DesktopModeStatus {
}
/**
+ * Return the maximum size of the window decoration surface control view host pool, or zero if
+ * there should be no pooling.
+ */
+ public static int getWindowDecorScvhPoolSize(@NonNull Context context) {
+ if (!Flags.enableDesktopWindowingScvhCacheBugFix()) return 0;
+ final int maxTaskLimit = getMaxTaskLimit(context);
+ if (maxTaskLimit > 0) {
+ return maxTaskLimit;
+ }
+ // TODO: b/368032552 - task limit equal to 0 means unlimited. Figure out what the pool
+ // size should be in that case.
+ return 0;
+ }
+
+ /**
* Return {@code true} if the current device supports desktop mode.
*/
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 5a2a723cacee..f9f43bc8dfae 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -85,6 +85,9 @@ public class SplitScreenConstants {
public @interface SplitIndex {
}
+ /** Signifies that user is currently not in split screen. */
+ public static final int NOT_IN_SPLIT = -1;
+
/**
* A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
* only used on tablets.
@@ -152,6 +155,23 @@ public class SplitScreenConstants {
public @interface PersistentSnapPosition {}
/**
+ * These are all the valid "states" that split screen can be in. It's the set of
+ * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
+ */
+ @IntDef(value = {
+ NOT_IN_SPLIT,
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
+ })
+ public @interface SplitScreenState {}
+
+ /**
* Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
*/
public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d2cef4baf798..f269b3831aab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -268,10 +268,7 @@ class ActivityEmbeddingAnimationRunner {
final Animation animation =
animationProvider.get(info, change, openingWholeScreenBounds);
if (shouldUseJumpCutForAnimation(animation)) {
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- return new ArrayList<>();
- }
- continue;
+ return new ArrayList<>();
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
info, change, animation, openingWholeScreenBounds);
@@ -296,10 +293,7 @@ class ActivityEmbeddingAnimationRunner {
final Animation animation =
animationProvider.get(info, change, closingWholeScreenBounds);
if (shouldUseJumpCutForAnimation(animation)) {
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- return new ArrayList<>();
- }
- continue;
+ return new ArrayList<>();
}
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
info, change, animation, closingWholeScreenBounds);
@@ -455,11 +449,9 @@ class ActivityEmbeddingAnimationRunner {
final Animation[] animations =
mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds);
// Jump cut if either animation has zero for duration.
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- for (Animation animation : animations) {
- if (shouldUseJumpCutForAnimation(animation)) {
- return new ArrayList<>();
- }
+ for (Animation animation : animations) {
+ if (shouldUseJumpCutForAnimation(animation)) {
+ return new ArrayList<>();
}
}
// Keep track as we might need to add background color for the animation.
@@ -516,10 +508,8 @@ class ActivityEmbeddingAnimationRunner {
mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds);
shouldShowBackgroundColor = false;
}
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- if (shouldUseJumpCutForAnimation(animation)) {
- return new ArrayList<>();
- }
+ if (shouldUseJumpCutForAnimation(animation)) {
+ return new ArrayList<>();
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
TransitionUtil.getRootFor(change, info)));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 3046307702c2..77799e99607b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -96,11 +96,9 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
- if (customAnimation != null) {
- return customAnimation;
- }
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return customAnimation;
}
// Use end bounds for opening.
final Rect bounds = change.getEndAbsBounds();
@@ -130,11 +128,9 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
- if (customAnimation != null) {
- return customAnimation;
- }
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return customAnimation;
}
// Use start bounds for closing.
final Rect bounds = change.getStartAbsBounds();
@@ -168,14 +164,12 @@ class ActivityEmbeddingAnimationSpec {
@NonNull
Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- // TODO(b/293658614): Support more complicated animations that may need more than a noop
- // animation as the start leash.
- final Animation noopAnimation = createNoopAnimation(change);
- final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
- if (customAnimation != null) {
- return new Animation[]{noopAnimation, customAnimation};
- }
+ // TODO(b/293658614): Support more complicated animations that may need more than a noop
+ // animation as the start leash.
+ final Animation noopAnimation = createNoopAnimation(change);
+ final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+ if (customAnimation != null) {
+ return new Animation[]{noopAnimation, customAnimation};
}
// Both start bounds and end bounds are in screen coordinates. We will post translate
// to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
@@ -320,13 +314,9 @@ class ActivityEmbeddingAnimationSpec {
}
final Animation anim;
- if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
- // TODO(b/293658614): Consider allowing custom animations from non-default packages.
- // Enforce limiting to animations from the default "android" package for now.
- anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
- } else {
- anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId);
- }
+ // TODO(b/293658614): Consider allowing custom animations from non-default packages.
+ // Enforce limiting to animations from the default "android" package for now.
+ anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
if (anim != null) {
return anim;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS
new file mode 100644
index 000000000000..84596b015209
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS
@@ -0,0 +1,6 @@
+# WM shell sub-module automotive owners
+
+winsonc@google.com
+stenning@google.com
+gauravbhola@google.com
+xiangw@google.com \ No newline at end of file
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 e9cfd9bc2209..60a52a808a54 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
@@ -32,7 +32,6 @@ import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.window.flags.Flags.unifyBackNavigationTransition;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -215,6 +214,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
setTriggerBack(false);
+ // Trigger close transition if necessary.
+ if (Flags.migratePredictiveBackTransition()) {
+ mBackTransitionHandler.onAnimationFinished();
+ }
resetTouchTracker();
// Don't wait for animation start
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
@@ -308,7 +311,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
updateEnableAnimationFromFlags();
createAdapter();
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mShellController.addConfigurationChangeListener(this);
@@ -552,6 +555,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// start animation immediately for non-gestural sources (without ACTION_MOVE
// events)
mThresholdCrossed = true;
+ mPointersPilfered = true;
onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
} else {
@@ -731,6 +735,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
callback.onBackStarted(backEvent);
if (mBackTransitionHandler.canHandOffAnimation()) {
callback.setHandoffHandler(mHandoffHandler);
+ } else {
+ callback.setHandoffHandler(null);
}
mOnBackStartDispatched = true;
} catch (RemoteException e) {
@@ -1273,14 +1279,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull SurfaceControl.Transaction st,
@NonNull SurfaceControl.Transaction ft,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final boolean isPrepareTransition =
- info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
- if (isPrepareTransition) {
- if (checkTakeoverFlags()) {
- mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info);
- }
- kickStartAnimation();
- }
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
// need to post to ShellExecutor when called.
if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
@@ -1308,7 +1306,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// animation never start, consume directly
applyAndFinish(st, ft, finishCallback);
return true;
- } else if (mClosePrepareTransition == null && isPrepareTransition) {
+ } else if (mClosePrepareTransition == null
+ && info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
// Gesture animation was cancelled before prepare transition ready, create
// the close prepare transition
createClosePrepareTransition();
@@ -1316,6 +1315,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
if (handlePrepareTransition(info, st, ft, finishCallback)) {
+ if (checkTakeoverFlags()) {
+ mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info);
+ }
+ kickStartAnimation();
return true;
}
return handleCloseTransition(info, st, ft, finishCallback);
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 b82496e45415..bec73a1500a7 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
@@ -35,7 +35,6 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import android.annotation.BinderThread;
import android.annotation.NonNull;
@@ -274,8 +273,11 @@ public class BubbleController implements ConfigurationChangeListener,
private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
- /** Used to track previous navigation mode to detect switch to buttons navigation. */
- private boolean mIsPrevNavModeGestures;
+ /**
+ * Used to track previous navigation mode to detect switch to buttons navigation. Set to
+ * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot.
+ */
+ private boolean mIsPrevNavModeGestures = true;
/** Used to send updates to the views from {@link #mBubbleDataListener}. */
private BubbleViewCallback mBubbleViewCallback;
@@ -357,7 +359,6 @@ public class BubbleController implements ConfigurationChangeListener,
}
};
mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
- mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -520,7 +521,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
mShellController.addConfigurationChangeListener(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+ mShellController.addExternalInterface(IBubbles.DESCRIPTOR,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
}
@@ -593,9 +594,9 @@ public class BubbleController implements ConfigurationChangeListener,
if (mBubbleStateListener != null) {
boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
- BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+ BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext)
? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
- mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+ mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
}
mIsPrevNavModeGestures = isCurrentNavModeGestures;
BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
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 0fd4206c0545..de85d9af127d 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
@@ -163,8 +163,11 @@ public class BubblePositioner {
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
} else {
- mExpandedViewLargeScreenWidth =
- res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
+ int expandedViewLargeScreenSpacing = res.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding);
+ mExpandedViewLargeScreenWidth = Math.min(
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width),
+ bounds.width() - expandedViewLargeScreenSpacing * 2);
}
if (mDeviceConfig.isLargeScreen()) {
if (mDeviceConfig.isSmallTablet()) {
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 a313bd004a51..4d7c7fad53f8 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles.bar;
import static android.view.View.ALPHA;
+import static android.view.View.INVISIBLE;
import static android.view.View.SCALE_X;
import static android.view.View.SCALE_Y;
import static android.view.View.TRANSLATION_X;
@@ -25,6 +26,7 @@ import static android.view.View.X;
import static android.view.View.Y;
import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
+import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_ALPHA;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE;
@@ -32,7 +34,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -42,11 +43,12 @@ import android.widget.FrameLayout;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
+import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
-import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget;
@@ -59,7 +61,7 @@ public class BubbleBarAnimationHelper {
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
- private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final int EXPANDED_VIEW_EXPAND_ALPHA_DURATION = 150;
private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400;
private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400;
private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
@@ -72,6 +74,17 @@ public class BubbleBarAnimationHelper {
private static final float DISMISS_VIEW_SCALE = 1.25f;
private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100;
+ private static final float SWITCH_OUT_SCALE = 0.97f;
+ private static final long SWITCH_OUT_SCALE_DURATION = 200L;
+ private static final long SWITCH_OUT_ALPHA_DURATION = 100L;
+ private static final long SWITCH_OUT_HANDLE_ALPHA_DURATION = 50L;
+ private static final long SWITCH_IN_ANIM_DELAY = 50L;
+ private static final long SWITCH_IN_TX_DURATION = 350L;
+ private static final long SWITCH_IN_ALPHA_DURATION = 50L;
+ // Keep this handle alpha delay at least as long as alpha animation for both expanded views.
+ private static final long SWITCH_IN_HANDLE_ALPHA_DELAY = 150L;
+ private static final long SWITCH_IN_HANDLE_ALPHA_DURATION = 100L;
+
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
new PhysicsAnimator.SpringConfig(300f, 0.9f);
@@ -80,68 +93,24 @@ public class BubbleBarAnimationHelper {
private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
new PhysicsAnimator.SpringConfig(900f, 1f);
+ private final int mSwitchAnimPositionOffset;
+
/** Matrix used to scale the expanded view container with a given pivot point. */
private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
- /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
- private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
-
@Nullable
- private Animator mRunningDragAnimator;
+ private Animator mRunningAnimator;
- private final Context mContext;
- private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
private final int[] mTmpLocation = new int[2];
+ // TODO(b/381936992): remove expanded bubble state from this helper class
private BubbleViewProvider mExpandedBubble;
- private boolean mIsExpanded = false;
- public BubbleBarAnimationHelper(Context context,
- BubbleBarLayerView bubbleBarLayerView,
- BubblePositioner positioner) {
- mContext = context;
- mLayerView = bubbleBarLayerView;
+ public BubbleBarAnimationHelper(Context context, BubblePositioner positioner) {
mPositioner = positioner;
-
- mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
- mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // We need to be Z ordered on top in order for alpha animations to work.
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setAnimating(true);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // The surface needs to be Z ordered on top for alpha values to work on the
- // TaskView, and if we're temporarily hidden, we are still on the screen
- // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
- // = 0f remains in effect.
- if (mIsExpanded) {
- bbev.setSurfaceZOrderedOnTop(false);
- }
-
- bbev.setContentVisibility(mIsExpanded);
- bbev.setAnimating(false);
- }
- }
- });
- mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- float alpha = (float) valueAnimator.getAnimatedValue();
- bbev.setTaskViewAlpha(alpha);
- bbev.setAlpha(alpha);
- }
- });
+ mSwitchAnimPositionOffset = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_switch_offset);
}
/**
@@ -154,18 +123,11 @@ public class BubbleBarAnimationHelper {
if (bbev == null) {
return;
}
- mIsExpanded = true;
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
- updateExpandedView();
- bbev.setAnimating(true);
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setContentVisibility(false);
- bbev.setAlpha(0f);
- bbev.setTaskViewAlpha(0f);
- bbev.setVisibility(VISIBLE);
+ prepareForAnimateIn(bbev);
setScaleFromBubbleBar(mExpandedViewContainerMatrix,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT);
@@ -173,7 +135,16 @@ public class BubbleBarAnimationHelper {
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
bbev.animateExpansionWhenTaskViewVisible(() -> {
- mExpandedViewAlphaAnimator.start();
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ true);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
@@ -188,7 +159,7 @@ public class BubbleBarAnimationHelper {
})
.withEndActions(() -> {
bbev.setAnimationMatrix(null);
- updateExpandedView();
+ updateExpandedView(bbev);
if (afterAnimation != null) {
afterAnimation.run();
}
@@ -197,13 +168,24 @@ public class BubbleBarAnimationHelper {
});
}
+ private void prepareForAnimateIn(BubbleBarExpandedView bbev) {
+ bbev.setAnimating(true);
+ updateExpandedView(bbev);
+ // We need to be Z ordered on top in order for taskView alpha to work.
+ // It is also set when the alpha animation starts, but needs to be set here to too avoid
+ // flickers.
+ bbev.setSurfaceZOrderedOnTop(true);
+ bbev.setTaskViewAlpha(0f);
+ bbev.setContentVisibility(false);
+ bbev.setVisibility(VISIBLE);
+ }
+
/**
* Collapses the currently expanded bubble.
*
* @param endRunnable a runnable to run at the end of the animation.
*/
public void animateCollapse(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate collapse without a bubble");
@@ -214,6 +196,19 @@ public class BubbleBarAnimationHelper {
setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f);
+ bbev.setAnimating(true);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
+
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
.spring(AnimatableScaleMatrix.SCALE_X,
@@ -234,7 +229,6 @@ public class BubbleBarAnimationHelper {
}
})
.start();
- mExpandedViewAlphaAnimator.reverse();
}
private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
@@ -246,6 +240,142 @@ public class BubbleBarAnimationHelper {
}
/**
+ * Animate between two bubble views using a switch animation
+ *
+ * @param fromBubble bubble to hide
+ * @param toBubble bubble to show
+ * @param afterAnimation optional runnable after animation finishes
+ */
+ public void animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble,
+ @Nullable Runnable afterAnimation) {
+ /*
+ * Switch animation
+ *
+ * |.....................fromBubble scale to 0.97.....................|
+ * |fromBubble handle alpha 0|....fromBubble alpha to 0.....| |
+ * 0-------------------------50-----------------------100---150--------200----------250--400
+ * |..toBubble alpha to 1...| |toBubble handle alpha 1| |
+ * |................toBubble position +/-48 to 0...............|
+ */
+
+ mExpandedBubble = toBubble;
+ final BubbleBarExpandedView toBbev = toBubble.getBubbleBarExpandedView();
+ final BubbleBarExpandedView fromBbev = fromBubble.getBubbleBarExpandedView();
+ if (toBbev == null || fromBbev == null) {
+ return;
+ }
+
+ fromBbev.setAnimating(true);
+
+ prepareForAnimateIn(toBbev);
+ final float endTx = toBbev.getTranslationX();
+ final float startTx = getSwitchAnimationInitialTx(endTx);
+ toBbev.setTranslationX(startTx);
+ toBbev.getHandleView().setAlpha(0f);
+
+ toBbev.animateExpansionWhenTaskViewVisible(() -> {
+ AnimatorSet switchAnim = new AnimatorSet();
+ switchAnim.playTogether(switchOutAnimator(fromBbev), switchInAnimator(toBbev, endTx));
+
+ switchAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ }
+ });
+ startNewAnimator(switchAnim);
+ });
+ }
+
+ private float getSwitchAnimationInitialTx(float endTx) {
+ if (mPositioner.isBubbleBarOnLeft()) {
+ return endTx - mSwitchAnimPositionOffset;
+ } else {
+ return endTx + mSwitchAnimPositionOffset;
+ }
+ }
+
+ private Animator switchOutAnimator(BubbleBarExpandedView bbev) {
+ setPivotToCenter(bbev);
+ AnimatorSet scaleAnim = new AnimatorSet();
+ scaleAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, SWITCH_OUT_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, SWITCH_OUT_SCALE)
+ );
+ scaleAnim.setInterpolator(Interpolators.ACCELERATE);
+ scaleAnim.setDuration(SWITCH_OUT_SCALE_DURATION);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setStartDelay(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+ alphaAnim.setDuration(SWITCH_OUT_ALPHA_DURATION);
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f)
+ .setDuration(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(scaleAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ return animator;
+ }
+
+ private Animator switchInAnimator(BubbleBarExpandedView bbev, float restingTx) {
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(bbev, TRANSLATION_X, restingTx);
+ positionAnim.setInterpolator(Interpolators.EMPHASIZED_DECELERATE);
+ positionAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ positionAnim.setDuration(SWITCH_IN_TX_DURATION);
+
+ // Animate alpha directly to have finer control over surface z-ordering
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bbev, TASK_VIEW_ALPHA, 1f);
+ alphaAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ alphaAnim.setDuration(SWITCH_IN_ALPHA_DURATION);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setContentVisibility(true);
+ // The outgoing expanded view alpha animation is still in progress.
+ // Do not reset the surface z-order as otherwise the outgoing expanded view is
+ // placed on top.
+ }
+ });
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f);
+ handleAlphaAnim.setStartDelay(SWITCH_IN_HANDLE_ALPHA_DELAY);
+ handleAlphaAnim.setDuration(SWITCH_IN_HANDLE_ALPHA_DURATION);
+ handleAlphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(false);
+ bbev.setAnimating(false);
+ }
+ });
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(positionAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateExpandedView(bbev);
+ }
+ });
+ return animator;
+ }
+
+
+ /**
* Animate the expanded bubble when it is being dragged
*/
public void animateStartDrag() {
@@ -273,7 +403,7 @@ public class BubbleBarAnimationHelper {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(contentAnim, handleAnim);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -282,7 +412,6 @@ public class BubbleBarAnimationHelper {
* @param endRunnable a runnable to run at the end of the animation
*/
public void animateDismiss(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate dismiss without a bubble");
@@ -335,7 +464,7 @@ public class BubbleBarAnimationHelper {
bbev.setDragging(false);
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -409,7 +538,7 @@ public class BubbleBarAnimationHelper {
}
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -435,7 +564,7 @@ public class BubbleBarAnimationHelper {
animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
EMPHASIZED_DECELERATE);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -443,14 +572,15 @@ public class BubbleBarAnimationHelper {
*/
public void cancelAnimations() {
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- mExpandedViewAlphaAnimator.cancel();
BubbleBarExpandedView bbev = getExpandedView();
if (bbev != null) {
bbev.animate().cancel();
}
- if (mRunningDragAnimator != null) {
- mRunningDragAnimator.cancel();
- mRunningDragAnimator = null;
+ if (mRunningAnimator != null) {
+ if (mRunningAnimator.isRunning()) {
+ mRunningAnimator.cancel();
+ }
+ mRunningAnimator = null;
}
}
@@ -462,8 +592,7 @@ public class BubbleBarAnimationHelper {
return null;
}
- private void updateExpandedView() {
- BubbleBarExpandedView bbev = getExpandedView();
+ private void updateExpandedView(BubbleBarExpandedView bbev) {
if (bbev == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
return;
@@ -477,6 +606,8 @@ public class BubbleBarAnimationHelper {
bbev.setLayoutParams(lp);
bbev.setX(position.x);
bbev.setY(position.y);
+ bbev.setScaleX(1f);
+ bbev.setScaleY(1f);
bbev.updateLocation();
bbev.maybeShowOverflow();
}
@@ -500,18 +631,54 @@ public class BubbleBarAnimationHelper {
return new Size(width, height);
}
- private void startNewDragAnimation(Animator animator) {
+ private void startNewAnimator(Animator animator) {
cancelAnimations();
- mRunningDragAnimator = animator;
+ mRunningAnimator = animator;
animator.start();
}
+ /**
+ * Animate the alpha of the expanded view between visible (1) and invisible (0).
+ * {@link BubbleBarExpandedView} requires
+ * {@link com.android.wm.shell.bubbles.BubbleExpandedView#setSurfaceZOrderedOnTop(boolean)} to
+ * be called before alpha can be applied.
+ * Only supports alpha of 1 or 0. Otherwise we can't reset surface z-order at the end.
+ */
+ private ObjectAnimator createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView,
+ boolean visible) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(bubbleBarExpandedView, TASK_VIEW_ALPHA,
+ visible ? 1f : 0f);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Move task view to the top of the window so alpha can be applied to it
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bubbleBarExpandedView.setContentVisibility(visible);
+ if (!visible) {
+ // Hide the expanded view before we reset the z-ordering
+ bubbleBarExpandedView.setVisibility(INVISIBLE);
+ }
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(false);
+ }
+ });
+ return animator;
+ }
+
private static void setDragPivot(BubbleBarExpandedView bbev) {
bbev.setPivotX(bbev.getWidth() / 2f);
bbev.setPivotY(0f);
}
- private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
+ private static void setPivotToCenter(BubbleBarExpandedView bbev) {
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(bbev.getHeight() / 2f);
+ }
+
+ private static class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
private final BubbleBarExpandedView mBubbleBarExpandedView;
@@ -523,11 +690,9 @@ public class BubbleBarAnimationHelper {
public void onAnimationStart(Animator animation) {
mBubbleBarExpandedView.setAnimating(true);
}
-
@Override
public void onAnimationEnd(Animator animation) {
mBubbleBarExpandedView.setAnimating(false);
- mRunningDragAnimator = null;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index ed49417ad3bd..2c0483c50710 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -85,6 +85,22 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
}
};
+ /**
+ * Property to set alpha for the task view
+ */
+ public static final FloatProperty<BubbleBarExpandedView> TASK_VIEW_ALPHA = new FloatProperty<>(
+ "taskViewAlpha") {
+ @Override
+ public void setValue(BubbleBarExpandedView bbev, float alpha) {
+ bbev.setTaskViewAlpha(alpha);
+ }
+
+ @Override
+ public Float get(BubbleBarExpandedView bbev) {
+ return bbev.mTaskView != null ? bbev.mTaskView.getAlpha() : bbev.getAlpha();
+ }
+ };
+
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
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 0c05e3c5115c..425afbed0742 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
@@ -103,8 +103,7 @@ public class BubbleBarLayerView extends FrameLayout
mPositioner = mBubbleController.getPositioner();
mBubbleLogger = bubbleLogger;
- mAnimationHelper = new BubbleBarAnimationHelper(context,
- this, mPositioner);
+ mAnimationHelper = new BubbleBarAnimationHelper(context, mPositioner);
mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> {
if (mExpandedView == null) return;
mExpandedView.setObscured(visible);
@@ -183,8 +182,14 @@ public class BubbleBarLayerView extends FrameLayout
// Already showing this bubble, skip animating
return;
}
+ BubbleViewProvider previousBubble = null;
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
- removeView(mExpandedView);
+ if (mIsExpanded) {
+ // Previous expanded view open, keep it visible to animate the switch
+ previousBubble = mExpandedBubble;
+ } else {
+ removeView(mExpandedView);
+ }
mExpandedView = null;
}
if (mExpandedView == null) {
@@ -246,7 +251,8 @@ public class BubbleBarLayerView extends FrameLayout
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+
+ final Runnable afterAnimation = () -> {
if (mExpandedView == null) return;
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
@@ -256,7 +262,18 @@ public class BubbleBarLayerView extends FrameLayout
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
setTouchDelegate(mHandleTouchDelegate);
- });
+ };
+
+ if (previousBubble != null) {
+ final BubbleBarExpandedView previousExpandedView =
+ previousBubble.getBubbleBarExpandedView();
+ mAnimationHelper.animateSwitch(previousBubble, mExpandedBubble, () -> {
+ removeView(previousExpandedView);
+ afterAnimation.run();
+ });
+ } else {
+ mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation);
+ }
showScrim(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt
new file mode 100644
index 000000000000..498d0e406e4b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.common
+
+import android.os.Looper
+import java.util.concurrent.Executor
+
+/** Executor implementation which can be boosted temporarily to a different thread priority. */
+interface BoostExecutor : Executor {
+ /**
+ * Requests that the executor is boosted until {@link #resetBoost()} is called.
+ */
+ fun setBoost() {}
+
+ /**
+ * Requests that the executor is not boosted (only resets if there are no other boost requests
+ * in progress).
+ */
+ fun resetBoost() {}
+
+ /**
+ * Returns whether the executor is boosted.
+ */
+ fun isBoosted() : Boolean {
+ return false
+ }
+
+ /**
+ * Returns the looper for this executor.
+ */
+ fun getLooper() : Looper? {
+ return Looper.myLooper()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 12d20bf0e517..f532be6b8277 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -96,14 +96,6 @@ public class DisplayController {
}
/**
- * Get all the displays from DisplayManager.
- */
- public Display[] getDisplays() {
- final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- return displayManager.getDisplays();
- }
-
- /**
* Gets the DisplayLayout associated with a display.
*/
public @Nullable DisplayLayout getDisplayLayout(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 38087c066918..ec3c0b83fe2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -228,7 +228,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
boolean mImeRequestedVisible =
(WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0;
InsetsSourceControl mImeSourceControl = null;
@@ -403,8 +402,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Override
// TODO(b/335404678): pass control target
- public void setImeInputTargetRequestedVisibility(boolean visible) {
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE);
mImeRequestedVisible = visible;
dispatchImeRequested(mDisplayId, mImeRequestedVisible);
@@ -414,21 +416,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
// therefore have to start the show animation from here
startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */);
- setVisibleDirectly(mImeRequestedVisible || mAnimation != null);
+ setVisibleDirectly(mImeRequestedVisible || mAnimation != null, statsToken);
}
}
/**
* Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
*/
- private void setVisibleDirectly(boolean visible) {
+ private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) {
mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
- mRequestedVisibleTypes = visible
- ? mRequestedVisibleTypes | WindowInsets.Type.ime()
- : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
+ int visibleTypes = visible ? WindowInsets.Type.ime() : 0;
try {
mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
- mRequestedVisibleTypes);
+ visibleTypes, WindowInsets.Type.ime(), statsToken);
} catch (RemoteException e) {
}
}
@@ -640,7 +640,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
t.hide(animatingLeash);
removeImeSurface(mDisplayId);
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- setVisibleDirectly(false /* visible */);
+ setVisibleDirectly(false /* visible */, statsToken);
}
ImeTracker.forLogging().onHidden(mStatsToken);
} else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
@@ -669,13 +669,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) {
// When going away, queue up insets change first, otherwise any bounds changes
// can have a "flicker" of ime-provided insets.
- setVisibleDirectly(false /* visible */);
+ setVisibleDirectly(false /* visible */, null /* statsToken */);
}
mAnimation.start();
if (!android.view.inputmethod.Flags.refactorInsetsController() && show) {
// When showing away, queue up insets change last, otherwise any bounds changes
// can have a "flicker" of ime-provided insets.
- setVisibleDirectly(true /* visible */);
+ setVisibleDirectly(true /* visible */, null /* statsToken */);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index c4c177cbcc28..c45f09be8430 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
@@ -223,13 +224,14 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
}
- private void setImeInputTargetRequestedVisibility(boolean visible) {
+ private void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
return;
}
for (OnInsetsChangedListener listener : listeners) {
- listener.setImeInputTargetRequestedVisibility(visible);
+ listener.setImeInputTargetRequestedVisibility(visible, statsToken);
}
}
@@ -276,10 +278,11 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
@Override
- public void setImeInputTargetRequestedVisibility(boolean visible)
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken)
throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.setImeInputTargetRequestedVisibility(visible);
+ PerDisplay.this.setImeInputTargetRequestedVisibility(visible, statsToken);
});
}
}
@@ -345,7 +348,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
* Called to set the requested visibility of the IME in DisplayImeController. Invoked by
* {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}.
* @param visible requested status of the IME
+ * @param statsToken the token tracking the current IME request
*/
- default void setImeInputTargetRequestedVisibility(boolean visible) {}
+ default void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index 736d954513b1..803f16ce39c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -16,15 +16,50 @@
package com.android.wm.shell.common;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.setThreadPriority;
+
import android.annotation.NonNull;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.function.BiConsumer;
/** Executor implementation which is backed by a Handler. */
public class HandlerExecutor implements ShellExecutor {
+ @NonNull
private final Handler mHandler;
+ // See android.os.Process#THREAD_PRIORITY_*
+ private final int mDefaultThreadPriority;
+ private final int mBoostedThreadPriority;
+ // Number of current requests to boost thread priority
+ private int mBoostCount;
+ private final Object mBoostLock = new Object();
+ // Default function for setting thread priority (tid, priority)
+ private BiConsumer<Integer, Integer> mSetThreadPriorityFn =
+ HandlerExecutor::setThreadPriorityInternal;
public HandlerExecutor(@NonNull Handler handler) {
+ this(handler, THREAD_PRIORITY_DEFAULT, THREAD_PRIORITY_DEFAULT);
+ }
+
+ /**
+ * Used only if this executor can be boosted, if so, it can be boosted to the given
+ * {@param boostPriority}.
+ */
+ public HandlerExecutor(@NonNull Handler handler, int defaultThreadPriority,
+ int boostedThreadPriority) {
mHandler = handler;
+ mDefaultThreadPriority = defaultThreadPriority;
+ mBoostedThreadPriority = boostedThreadPriority;
+ }
+
+ @VisibleForTesting
+ void replaceSetThreadPriorityFn(BiConsumer<Integer, Integer> setThreadPriorityFn) {
+ mSetThreadPriorityFn = setThreadPriorityFn;
}
@Override
@@ -56,9 +91,54 @@ public class HandlerExecutor implements ShellExecutor {
}
@Override
+ public void setBoost() {
+ synchronized (mBoostLock) {
+ if (mDefaultThreadPriority == mBoostedThreadPriority) {
+ // Nothing to boost
+ return;
+ }
+ if (mBoostCount == 0) {
+ mSetThreadPriorityFn.accept(
+ ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(),
+ mBoostedThreadPriority);
+ }
+ mBoostCount++;
+ }
+ }
+
+ @Override
+ public void resetBoost() {
+ synchronized (mBoostLock) {
+ mBoostCount--;
+ if (mBoostCount == 0) {
+ mSetThreadPriorityFn.accept(
+ ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(),
+ mDefaultThreadPriority);
+ }
+ }
+ }
+
+ @Override
+ public boolean isBoosted() {
+ synchronized (mBoostLock) {
+ return mBoostCount > 0;
+ }
+ }
+
+ @Override
+ @NonNull
+ public Looper getLooper() {
+ return mHandler.getLooper();
+ }
+
+ @Override
public void assertCurrentThread() {
if (!mHandler.getLooper().isCurrentThread()) {
throw new IllegalStateException("must be called on " + mHandler);
}
}
+
+ private static void setThreadPriorityInternal(Integer tid, Integer priority) {
+ setThreadPriority(tid, priority);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 2c2961fd4b65..9e5071e8347b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -18,15 +18,15 @@ package com.android.wm.shell.common;
import java.lang.reflect.Array;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Super basic Executor interface that adds support for delayed execution and removing callbacks.
- * Intended to wrap Handler while better-supporting testing.
+ * Intended to wrap Handler while better-supporting testing. Not every ShellExecutor implementation
+ * may support boosting.
*/
-public interface ShellExecutor extends Executor {
+public interface ShellExecutor extends BoostExecutor {
/**
* Executes the given runnable. If the caller is running on the same looper as this executor,
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 6beff1979e6d..1852cda7e804 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
@@ -55,6 +55,7 @@ import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
+import android.util.Log;
import android.view.Display;
import android.view.InsetsController;
import android.view.InsetsSource;
@@ -142,6 +143,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@ShellMainThread
private final Handler mHandler;
+ /** Singleton source of truth for the current state of split screen on this device. */
+ private final SplitState mSplitState;
+
private int mDividerWindowWidth;
private int mDividerInsets;
private int mDividerSize;
@@ -204,7 +208,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayController displayController, DisplayImeController displayImeController,
- ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+ ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState,
+ @ShellMainThread Handler handler) {
mHandler = handler;
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
@@ -220,6 +225,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ mSplitState = splitState;
final Resources res = mContext.getResources();
mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
@@ -381,6 +387,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
}
+ /** Updates the {@link SplitState} using the current divider position. */
+ public void updateStateWithCurrentPosition() {
+ mSplitState.set(calculateCurrentSnapPosition());
+ }
+
/**
* Returns the divider position as a fraction from 0 to 1.
*/
@@ -413,7 +424,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
removeTouchZones();
}
- int currentPosition = calculateCurrentSnapPosition();
+ int currentPosition = mSplitState.get();
+ // TODO (b/349828130): Can delete this warning after brief soak time.
+ if (currentPosition != calculateCurrentSnapPosition()) {
+ Log.wtf(TAG, "SplitState is " + mSplitState.get()
+ + ", expected " + calculateCurrentSnapPosition());
+ }
+
switch (currentPosition) {
case SNAP_TO_2_10_90:
case SNAP_TO_3_10_45_45:
@@ -764,7 +781,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
break;
default:
flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
- () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
+ () -> {
+ setDividerPosition(snapTarget.position, true /* applyLayoutChange */);
+ mSplitState.set(snapTarget.snapPosition);
+ });
break;
}
}
@@ -836,10 +856,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
/** Fling divider from current position to center position. */
public void flingDividerToCenter(@Nullable Runnable finishCallback) {
- final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+ final SnapTarget target = mDividerSnapAlgorithm.getMiddleTarget();
+ final int pos = target.position;
flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
() -> {
setDividerPosition(pos, true /* applyLayoutChange */);
+ mSplitState.set(target.snapPosition);
if (finishCallback != null) {
finishCallback.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
new file mode 100644
index 000000000000..71758e0d2159
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -0,0 +1,42 @@
+/*
+ * 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.common.split;
+
+import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
+
+/**
+ * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions.
+ */
+public class SplitState {
+ private @SplitScreenState int mState = NOT_IN_SPLIT;
+
+ /** Updates the current state of split screen on this device. */
+ public void set(@SplitScreenState int newState) {
+ mState = newState;
+ }
+
+ /** Reports the current state of split screen on this device. */
+ public @SplitScreenState int get() {
+ return mState;
+ }
+
+ /** Sets NOT_IN_SPLIT when user exits split. */
+ public void exit() {
+ set(NOT_IN_SPLIT);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 62d5098f2a27..bc56637b2a1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -30,7 +30,7 @@ import com.android.internal.R
* desktop windowing environment.
*/
fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+ (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
&& !task.isTopActivityNoDisplay
private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
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 c99d9ba862c1..9d4b4bbb33de 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
@@ -54,6 +54,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.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -65,6 +66,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -194,7 +196,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final CompatUIStatusManager mCompatUIStatusManager;
@NonNull
- private final IntPredicate mInDesktopModePredicate;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@@ -210,7 +212,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
@NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
@NonNull AccessibilityManager accessibilityManager,
@NonNull CompatUIStatusManager compatUIStatusManager,
- @NonNull IntPredicate isDesktopModeEnablePredicate) {
+ @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -226,7 +228,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
DISAPPEAR_DELAY_MS, flags);
mCompatUIStatusManager = compatUIStatusManager;
- mInDesktopModePredicate = isDesktopModeEnablePredicate;
+ mDesktopUserRepositories = desktopUserRepositories;
shellInit.addInitCallback(this::onInit, this);
}
@@ -267,7 +269,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
updateActiveTaskInfo(taskInfo);
}
-
// We're showing the first reachability education so we ignore incoming TaskInfo
// until the education flow has completed or we double tap. The double-tap
// basically cancel all the onboarding flow. We don't have to ignore events in case
@@ -865,7 +866,11 @@ public class CompatUIController implements OnDisplaysChangedListener,
}
private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) {
- return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode()
- && mInDesktopModePredicate.test(taskInfo.displayId);
+ if (mDesktopUserRepositories.isEmpty() || taskInfo == null) {
+ return false;
+ }
+ boolean isDesktopModeShowing = mDesktopUserRepositories.get().getCurrent()
+ .getVisibleTaskCount(taskInfo.displayId) > 0;
+ return Flags.skipCompatUiEducationInDesktopMode() && isDesktopModeShowing;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 2128cbc144b5..0d16880aec3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.util.Pair;
import android.view.LayoutInflater;
+import android.view.SurfaceControl;
import android.view.View;
import android.window.DesktopModeFlags;
@@ -70,6 +71,9 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
private final float mHideScmTolerance;
+ @NonNull
+ private final Rect mLayoutBounds = new Rect();
+
CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
@NonNull SyncTransactionQueue syncQueue,
@NonNull Consumer<CompatUIEvent> callback,
@@ -105,6 +109,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected void removeLayout() {
+ mLayoutBounds.setEmpty();
mLayout = null;
}
@@ -171,18 +176,21 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
@VisibleForTesting
public void updateSurfacePosition() {
- if (mLayout == null) {
+ updateLayoutBounds();
+ if (mLayoutBounds.isEmpty()) {
return;
}
- // Position of the button in the container coordinate.
- final Rect taskBounds = getTaskBounds();
- final Rect taskStableBounds = getTaskStableBounds();
- final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? taskStableBounds.left - taskBounds.left
- : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
- final int positionY = taskStableBounds.bottom - taskBounds.top
- - mLayout.getMeasuredHeight();
- updateSurfacePosition(positionX, positionY);
+ updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+ updateLayoutBounds();
+ if (mLayoutBounds.isEmpty()) {
+ return;
+ }
+ updateSurfaceBounds(tx, mLayoutBounds);
}
@VisibleForTesting
@@ -219,6 +227,23 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
return percentageAreaOfLetterboxInTask < mHideScmTolerance;
}
+ private void updateLayoutBounds() {
+ if (mLayout == null) {
+ mLayoutBounds.setEmpty();
+ return;
+ }
+ // Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ final int layoutWidth = mLayout.getMeasuredWidth();
+ final int layoutHeight = mLayout.getMeasuredHeight();
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - layoutWidth;
+ final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight;
+ mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight);
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index d2b4f1ab6b0d..82acfe5478b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -43,6 +43,7 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -327,8 +328,15 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
if (mViewHost == null) {
return;
}
- mViewHost.relayout(windowLayoutParams);
- updateSurfacePosition();
+ if (Flags.appCompatAsyncRelayout()) {
+ mViewHost.relayout(windowLayoutParams, tx -> {
+ updateSurfacePosition(tx);
+ tx.apply();
+ });
+ } else {
+ mViewHost.relayout(windowLayoutParams);
+ updateSurfacePosition();
+ }
}
@NonNull
@@ -349,6 +357,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
*/
protected abstract void updateSurfacePosition();
+ protected void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+
+ }
+
/**
* Updates the position of the surface with respect to the given {@code positionX} and {@code
* positionY}.
@@ -366,6 +378,15 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
});
}
+ protected void updateSurfaceBounds(@NonNull SurfaceControl.Transaction tx,
+ @NonNull Rect bounds) {
+ if (mLeash == null) {
+ return;
+ }
+ tx.setPosition(mLeash, bounds.left, bounds.top)
+ .setWindowCrop(mLeash, bounds.width(), bounds.height());
+ }
+
protected int getLayoutDirection() {
return mContext.getResources().getConfiguration().getLayoutDirection();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index 3f67172ca636..650d2170ce00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.LayoutInflater;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -69,6 +70,9 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
@NonNull
final CompatUIHintsState mCompatUIHintsState;
+ @NonNull
+ private final Rect mLayoutBounds = new Rect();
+
@Nullable
private UserAspectRatioSettingsLayout mLayout;
@@ -108,6 +112,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
@Override
protected void removeLayout() {
+ mLayoutBounds.setEmpty();
mLayout = null;
}
@@ -168,18 +173,21 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
@Override
@VisibleForTesting
public void updateSurfacePosition() {
- if (mLayout == null) {
+ updateLayoutBounds();
+ if (mLayoutBounds.isEmpty()) {
return;
}
- // Position of the button in the container coordinate.
- final Rect taskBounds = getTaskBounds();
- final Rect taskStableBounds = getTaskStableBounds();
- final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? taskStableBounds.left - taskBounds.left
- : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
- final int positionY = taskStableBounds.bottom - taskBounds.top
- - mLayout.getMeasuredHeight();
- updateSurfacePosition(positionX, positionY);
+ updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+ updateLayoutBounds();
+ if (mLayoutBounds.isEmpty()) {
+ return;
+ }
+ updateSurfaceBounds(tx, mLayoutBounds);
}
@VisibleForTesting
@@ -202,6 +210,23 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
&& !isHideDelayReached(mNextButtonHideTimeMs));
}
+ private void updateLayoutBounds() {
+ if (mLayout == null) {
+ mLayoutBounds.setEmpty();
+ return;
+ }
+ // Position of the button in the container coordinate.
+ final Rect taskBounds = getTaskBounds();
+ final Rect taskStableBounds = getTaskStableBounds();
+ final int layoutWidth = mLayout.getMeasuredWidth();
+ final int layoutHeight = mLayout.getMeasuredHeight();
+ final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? taskStableBounds.left - taskBounds.left
+ : taskStableBounds.right - taskBounds.left - layoutWidth;
+ final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight;
+ mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight);
+ }
+
private void showUserAspectRatioButton() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
index 819b11095a9b..2d0a3f54f85f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt
@@ -67,8 +67,8 @@ class LetterboxCommandHandler @Inject constructor(
return false
}
return when (args.size) {
- 1 -> onShellDisplayCommand(args[0], pw)
- 2 -> onShellUpdateCommand(args[0], args[1], pw)
+ 1 -> onNoParamsCommand(args[0], pw)
+ 2 -> onSingleParamCommand(args[0], args[1], pw)
else -> {
pw.println("Invalid command: " + args[0])
return false
@@ -89,11 +89,17 @@ class LetterboxCommandHandler @Inject constructor(
$prefix name, for example, @android:color/system_accent2_50.
$prefix backgroundColorReset"
$prefix Resets the background color to the default value."
+ $prefix cornerRadius"
+ $prefix Corners radius (in pixels) for activities in the letterbox mode."
+ $prefix If cornerRadius < 0, it will be ignored and corners of the"
+ $prefix activity won't be rounded."
+ $prefix cornerRadiusReset"
+ $prefix Resets the rounded corners radius to the default value."
""".trimIndent()
)
}
- private fun onShellUpdateCommand(command: String, value: String, pw: PrintWriter): Boolean {
+ private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean {
when (command) {
"backgroundColor" -> {
return invokeWhenValid(
@@ -120,10 +126,17 @@ class LetterboxCommandHandler @Inject constructor(
}
)
- "backgroundColorReset" -> {
- letterboxConfiguration.resetLetterboxBackgroundColor()
- return true
- }
+ "cornerRadius" -> return invokeWhenValid(
+ pw,
+ value,
+ ::strToInt{ it >= 0 },
+ { radius ->
+ letterboxConfiguration.setLetterboxActivityCornersRadius(radius)
+ },
+ { r ->
+ "$r is not a valid radius. It must be an integer >= 0."
+ }
+ )
else -> {
pw.println("Invalid command: $value")
@@ -132,7 +145,7 @@ class LetterboxCommandHandler @Inject constructor(
}
}
- private fun onShellDisplayCommand(command: String, pw: PrintWriter): Boolean {
+ private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean {
when (command) {
"backgroundColor" -> {
pw.println(
@@ -144,6 +157,24 @@ class LetterboxCommandHandler @Inject constructor(
return true
}
+ "backgroundColorReset" -> {
+ letterboxConfiguration.resetLetterboxBackgroundColor()
+ return true
+ }
+
+ "cornerRadius" -> {
+ pw.println(
+ " Rounded corners radius: " +
+ "${letterboxConfiguration.getLetterboxActivityCornersRadius()} px."
+ )
+ return true
+ }
+
+ "cornerRadiusReset" -> {
+ letterboxConfiguration.resetLetterboxActivityCornersRadius()
+ return true
+ }
+
else -> {
pw.println("Invalid command: $command")
return false
@@ -181,4 +212,15 @@ class LetterboxCommandHandler @Inject constructor(
} catch (e: IllegalArgumentException) {
null
}
+
+ // Converts a String to Int which if possible or it returns null otherwise.
+ // If a predicate is set, it also returns [null] if the predicate evaluate to [false].
+ private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str ->
+ try {
+ val value = str.toInt()
+ if (predicate(value)) value else null
+ } catch (e: IllegalArgumentException) {
+ null
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
index 83a8e3103e3f..244ff99cadd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
@@ -36,6 +36,21 @@ class LetterboxConfiguration @Inject constructor(
// Color resource id for the solid color letterbox background type.
private var letterboxBackgroundColorResourceIdOverride: Int? = null
+ // Default value for corners radius for activities presented in the letterbox mode.
+ // Values < 0 will be ignored.
+ private val letterboxActivityDefaultCornersRadius: Int
+
+ // Current corners radius for activities presented in the letterbox mode.
+ // Values can be modified at runtime and values < 0 will be ignored.
+ private var letterboxActivityCornersRadius = 0
+
+ init {
+ letterboxActivityDefaultCornersRadius = context.resources.getInteger(
+ R.integer.config_letterboxActivityCornersRadius
+ )
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
/**
* Sets color of letterbox background which is used when using the solid background mode.
*/
@@ -64,7 +79,7 @@ class LetterboxConfiguration @Inject constructor(
}
// Query color dynamically because material colors extracted from wallpaper are updated
// when wallpaper is changed.
- return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null))
+ return Color.valueOf(context.getResources().getColor(colorId!!, null))
}
/**
@@ -79,4 +94,33 @@ class LetterboxConfiguration @Inject constructor(
* The background color for the Letterbox.
*/
fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components
+
+ /**
+ * Overrides corners radius for activities presented in the letterbox mode. Values < 0,
+ * will be ignored and corners of the activity won't be rounded.
+ */
+ fun setLetterboxActivityCornersRadius(cornersRadius: Int) {
+ letterboxActivityCornersRadius = cornersRadius
+ }
+
+ /**
+ * Resets corners radius for activities presented in the letterbox mode.
+ */
+ fun resetLetterboxActivityCornersRadius() {
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
+ /**
+ * Whether corners of letterboxed activities are rounded.
+ */
+ fun isLetterboxActivityCornersRounded(): Boolean {
+ return getLetterboxActivityCornersRadius() > 0
+ }
+
+ /**
+ * Gets corners radius for activities presented in the letterbox mode.
+ */
+ fun getLetterboxActivityCornersRadius(): Int {
+ return maxOf(letterboxActivityCornersRadius, 0)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
index 523e2f5cf7dc..2c52e9ea3fc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -58,7 +58,8 @@ interface LetterboxController {
fun updateLetterboxSurfaceBounds(
key: LetterboxKey,
transaction: Transaction,
- taskBounds: Rect
+ taskBounds: Rect,
+ activityBounds: Rect
)
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
new file mode 100644
index 000000000000..0c3769e778cd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Encapsulate the logic related to the use of a single or multiple surfaces when
+ * implementing letterbox in shell.
+ */
+@WMSingleton
+class LetterboxControllerStrategy @Inject constructor(
+ private val letterboxConfiguration: LetterboxConfiguration
+) {
+
+ // Different letterbox implementation modes.
+ enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
+
+ @Volatile
+ private var currentMode: LetterboxMode = SINGLE_SURFACE
+
+ fun configureLetterboxMode() {
+ // TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
+ // At the moment, we use the presence of rounded corners to understand if to use a single
+ // surface or multiple surfaces for the letterbox areas. This rule will change when
+ // considering transparent activities which won't have rounded corners leading to the
+ // [MULTIPLE_SURFACES] option.
+ // The chosen strategy will depend on performance considerations,
+ // including surface memory usage and the impact of the rounded corners solution.
+ currentMode = if (letterboxConfiguration.isLetterboxActivityCornersRounded()) {
+ SINGLE_SURFACE
+ } else {
+ MULTIPLE_SURFACES
+ }
+ }
+
+ /**
+ * @return The specific mode to use for implementing letterboxing for the given [request].
+ */
+ fun getLetterboxImplementationMode(): LetterboxMode = currentMode
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
index adb034cc4787..771d61832889 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -16,5 +16,18 @@
package com.android.wm.shell.compatui.letterbox
+import android.view.SurfaceControl
+
// The key to use for identify the letterbox sessions.
-data class LetterboxKey(val displayId: Int, val taskId: Int) \ No newline at end of file
+data class LetterboxKey(val displayId: Int, val taskId: Int)
+
+// Encapsulates the surfaces in the multiple surfaces scenario.
+data class LetterboxSurfaces(
+ var leftSurface: SurfaceControl? = null,
+ var topSurface: SurfaceControl? = null,
+ var rightSurface: SurfaceControl? = null,
+ var bottomSurface: SurfaceControl? = null
+) : Iterable<SurfaceControl?> {
+ override fun iterator() =
+ listOf(leftSurface, topSurface, rightSurface, bottomSurface).iterator()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
index b50716ad07a3..47180718634c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -22,6 +22,7 @@ import android.view.SurfaceControl
import android.window.TransitionInfo
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.appCompatRefactoring
+import com.android.wm.shell.common.transition.TransitionStateHolder
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
import com.android.wm.shell.shared.TransitionUtil.isClosingType
import com.android.wm.shell.sysui.ShellInit
@@ -33,12 +34,16 @@ import com.android.wm.shell.transition.Transitions
class LetterboxTransitionObserver(
shellInit: ShellInit,
private val transitions: Transitions,
- private val letterboxController: LetterboxController
+ private val letterboxController: LetterboxController,
+ private val transitionStateHolder: TransitionStateHolder,
+ private val letterboxModeStrategy: LetterboxControllerStrategy
) : Transitions.TransitionObserver {
companion object {
@JvmStatic
private val TAG = "LetterboxTransitionObserver"
+ @JvmStatic
+ private val EMPTY_BOUNDS = Rect()
}
init {
@@ -59,7 +64,6 @@ class LetterboxTransitionObserver(
// We recognise the operation to execute and delegate to the LetterboxController
// the related operation.
// TODO(b/377875151): Identify Desktop Windowing Transactions.
- // TODO(b/377857898): Handling multiple surfaces
// TODO(b/371500295): Handle input events detection.
for (change in info.changes) {
change.taskInfo?.let { ti ->
@@ -71,23 +75,27 @@ class LetterboxTransitionObserver(
change.endAbsBounds.height()
)
with(letterboxController) {
- if (isClosingType(change.mode)) {
- destroyLetterboxSurface(
- key,
- startTransaction
- )
+ // TODO(b/380274087) Handle return to home from a recents transition.
+ if (isClosingType(change.mode) &&
+ !transitionStateHolder.isRecentsTransitionRunning()) {
+ // For the other types of close we need to check the recents.
+ destroyLetterboxSurface(key, finishTransaction)
} else {
val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
if (isTopActivityLetterboxed) {
+ letterboxModeStrategy.configureLetterboxMode()
createLetterboxSurface(
key,
startTransaction,
change.leash
)
+ val activityBounds =
+ ti.appCompatTaskInfo.topActivityLetterboxBounds ?: EMPTY_BOUNDS
updateLetterboxSurfaceBounds(
key,
startTransaction,
- taskBounds
+ taskBounds,
+ activityBounds
)
}
updateLetterboxSurfaceVisibility(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
new file mode 100644
index 000000000000..ef964f40dab3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+
+/**
+ * Creates a [LetterboxController] which is the composition of other two [LetterboxController].
+ * It basically invokes the method on both of them.
+ */
+infix fun LetterboxController.append(other: LetterboxController) = object : LetterboxController {
+ override fun createLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ this@append.createLetterboxSurface(key, transaction, parentLeash)
+ other.createLetterboxSurface(key, transaction, parentLeash)
+ }
+
+ override fun destroyLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction
+ ) {
+ this@append.destroyLetterboxSurface(key, transaction)
+ other.destroyLetterboxSurface(key, transaction)
+ }
+
+ override fun updateLetterboxSurfaceVisibility(
+ key: LetterboxKey,
+ transaction: Transaction,
+ visible: Boolean
+ ) {
+ this@append.updateLetterboxSurfaceVisibility(key, transaction, visible)
+ other.updateLetterboxSurfaceVisibility(key, transaction, visible)
+ }
+
+ override fun updateLetterboxSurfaceBounds(
+ key: LetterboxKey,
+ transaction: Transaction,
+ taskBounds: Rect,
+ activityBounds: Rect
+ ) {
+ this@append.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+ other.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+ }
+
+ override fun dump() {
+ this@append.dump()
+ other.dump()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
new file mode 100644
index 000000000000..8d065703c380
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * [LetterboxController] implementation working as coordinator of other [LetterboxController]
+ * implementations.
+ */
+@WMSingleton
+class MixedLetterboxController @Inject constructor(
+ private val singleSurfaceController: SingleSurfaceLetterboxController,
+ private val multipleSurfaceController: MultiSurfaceLetterboxController,
+ private val controllerStrategy: LetterboxControllerStrategy
+) : LetterboxController by singleSurfaceController append multipleSurfaceController {
+
+ override fun createLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ when (controllerStrategy.getLetterboxImplementationMode()) {
+ SINGLE_SURFACE -> {
+ multipleSurfaceController.destroyLetterboxSurface(key, transaction)
+ singleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+ }
+
+ MULTIPLE_SURFACES -> {
+ singleSurfaceController.destroyLetterboxSurface(key, transaction)
+ multipleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt
new file mode 100644
index 000000000000..5129d03b9dbc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of multiple letterbox surfaces when needed.
+ */
+@WMSingleton
+class MultiSurfaceLetterboxController @Inject constructor(
+ private val letterboxBuilder: LetterboxSurfaceBuilder
+) : LetterboxController {
+
+ companion object {
+ @JvmStatic
+ private val TAG = "MultiSurfaceLetterboxController"
+ }
+
+ private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxSurfaces>()
+
+ override fun createLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ val surfaceBuilderFn = { position: String ->
+ letterboxBuilder.createSurface(
+ transaction,
+ parentLeash,
+ "ShellLetterboxSurface-$key-$position",
+ "MultiSurfaceLetterboxController#createLetterboxSurface"
+ )
+ }
+ letterboxMap.runOnItem(key, onMissed = { k, m ->
+ m[k] = LetterboxSurfaces(
+ leftSurface = surfaceBuilderFn("Left"),
+ topSurface = surfaceBuilderFn("Top"),
+ rightSurface = surfaceBuilderFn("Right"),
+ bottomSurface = surfaceBuilderFn("Bottom"),
+ )
+ })
+ }
+
+ override fun destroyLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.forEach { s ->
+ s.remove(transaction)
+ }
+ })
+ letterboxMap.remove(key)
+ }
+
+ override fun updateLetterboxSurfaceVisibility(
+ key: LetterboxKey,
+ transaction: Transaction,
+ visible: Boolean
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.forEach { s ->
+ s.setVisibility(transaction, visible)
+ }
+ })
+ }
+
+ override fun updateLetterboxSurfaceBounds(
+ key: LetterboxKey,
+ transaction: Transaction,
+ taskBounds: Rect,
+ activityBounds: Rect
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.updateSurfacesBounds(transaction, taskBounds, activityBounds)
+ })
+ }
+
+ override fun dump() {
+ ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+ }
+
+ /*
+ * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+ */
+ private fun MutableMap<LetterboxKey, LetterboxSurfaces>.runOnItem(
+ key: LetterboxKey,
+ onFound: (LetterboxSurfaces) -> Unit = { _ -> },
+ onMissed: (
+ LetterboxKey,
+ MutableMap<LetterboxKey, LetterboxSurfaces>
+ ) -> Unit = { _, _ -> }
+ ) {
+ this[key]?.let {
+ return onFound(it)
+ }
+ return onMissed(key, this)
+ }
+
+ private fun SurfaceControl?.remove(
+ tx: Transaction
+ ) = this?.let {
+ tx.remove(this)
+ }
+
+ private fun SurfaceControl?.setVisibility(
+ tx: Transaction,
+ visible: Boolean
+ ) = this?.let {
+ tx.setVisibility(this, visible)
+ }
+
+ private fun Transaction.moveAndCrop(
+ surface: SurfaceControl,
+ rect: Rect
+ ): Transaction =
+ setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(
+ surface,
+ rect.width(),
+ rect.height()
+ )
+
+ private fun LetterboxSurfaces.updateSurfacesBounds(
+ tx: Transaction,
+ taskBounds: Rect,
+ activityBounds: Rect
+ ) {
+ // Update the bounds depending on the activity position.
+ leftSurface?.let { s ->
+ tx.moveAndCrop(
+ s,
+ Rect(taskBounds.left, taskBounds.top, activityBounds.left, taskBounds.bottom)
+ )
+ }
+ rightSurface?.let { s ->
+ tx.moveAndCrop(
+ s,
+ Rect(activityBounds.right, taskBounds.top, taskBounds.right, taskBounds.bottom)
+ )
+ }
+ topSurface?.let { s ->
+ tx.moveAndCrop(
+ s,
+ Rect(taskBounds.left, taskBounds.top, taskBounds.right, activityBounds.top)
+ )
+ }
+ bottomSurface?.let { s ->
+ tx.moveAndCrop(
+ s,
+ Rect(taskBounds.left, activityBounds.bottom, taskBounds.right, taskBounds.bottom)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
index f21a7272287e..a67f6082c892 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
@@ -34,7 +34,7 @@ class SingleSurfaceLetterboxController @Inject constructor(
companion object {
@JvmStatic
- private val TAG = "LetterboxController"
+ private val TAG = "SingleSurfaceLetterboxController"
}
private val letterboxMap = mutableMapOf<LetterboxKey, SurfaceControl>()
@@ -93,7 +93,8 @@ class SingleSurfaceLetterboxController @Inject constructor(
override fun updateLetterboxSurfaceBounds(
key: LetterboxKey,
transaction: Transaction,
- taskBounds: Rect
+ taskBounds: Rect,
+ activityBounds: Rect
) {
letterboxMap.runOnItem(key, onFound = { item ->
item.run {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt
new file mode 100644
index 000000000000..d5e0240ea9ad
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.dagger
+
+/**
+ * An interface implemented by the application that uses [WMComponent].
+ *
+ * This exposes the component to allow classes to do member injection for bindings where constructor
+ * injection is not possible, e.g. views.
+ */
+interface HasWMComponent {
+ fun getWMComponent(): WMComponent
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java
new file mode 100644
index 000000000000..76279ec8cfec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.dagger;
+
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.transition.TransitionStateHolder;
+import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy;
+import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
+import com.android.wm.shell.compatui.letterbox.MixedLetterboxController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides Letterbox Shell implementation components to Dagger dependency Graph.
+ */
+@Module
+public abstract class LetterboxModule {
+
+ @WMSingleton
+ @Provides
+ static LetterboxTransitionObserver provideLetterboxTransitionObserver(
+ @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions,
+ @NonNull LetterboxController letterboxController,
+ @NonNull TransitionStateHolder transitionStateHolder,
+ @NonNull LetterboxControllerStrategy letterboxControllerStrategy
+ ) {
+ return new LetterboxTransitionObserver(shellInit, transitions, letterboxController,
+ transitionStateHolder, letterboxControllerStrategy);
+ }
+
+ @WMSingleton
+ @Binds
+ abstract LetterboxController bindsLetterboxController(
+ MixedLetterboxController letterboxController);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 33e4fd8c1a46..aebd94fc173a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -30,6 +30,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.dagger.pip.TvPipModule;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
@@ -89,6 +90,7 @@ public class TvWMShellModule {
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
@@ -96,6 +98,6 @@ public class TvWMShellModule {
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
displayImeController, displayInsetsController, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper,
- mainExecutor, mainHandler, systemWindows);
+ splitState, mainExecutor, mainHandler, systemWindows);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
index a3cdb2eff601..c493aadd57b0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
@@ -14,17 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.dagger;
+package com.android.wm.shell.dagger;
import android.os.HandlerThread;
import androidx.annotation.Nullable;
-import com.android.systemui.SystemUIInitializer;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.dagger.WMShellModule;
-import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.keyguard.KeyguardTransitions;
@@ -45,12 +42,11 @@ import java.util.Optional;
/**
* Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
- * from the WM component into the SysUI component (in
- * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
+ * from the WM component into the SysUI component, and references the specific dependencies
* provided by its particular device/form-factor SystemUI implementation.
*
- * ie. {@link WMComponent} includes {@link WMShellModule}
- * and {@code TvWMComponent} includes {@link com.android.wm.shell.dagger.TvWMShellModule}
+ * <p> ie. {@link WMComponent} includes {@link WMShellModule} and {@code TvWMComponent} includes
+ * {@link TvWMShellModule}
*/
@WMSingleton
@Subcomponent(modules = {WMShellModule.class})
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 cb9c20e9b7ec..de86b22e0a99 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
@@ -72,6 +72,7 @@ import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -87,8 +88,8 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.freeform.FreeformComponents;
@@ -138,7 +139,6 @@ import dagger.Module;
import dagger.Provides;
import java.util.Optional;
-import java.util.function.IntPredicate;
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
@@ -267,7 +267,7 @@ public abstract class WMShellBaseModule {
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
@NonNull CompatUIState compatUIState,
@NonNull CompatUIComponentIdGenerator componentIdGenerator,
@NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -280,10 +280,6 @@ public abstract class WMShellBaseModule {
new DefaultCompatUIHandler(compatUIRepository, compatUIState,
componentIdGenerator, compatUIComponentFactory, mainExecutor));
}
- final IntPredicate inDesktopModePredicate =
- desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
- modeTaskRepository.getVisibleTaskCount(displayId) > 0)
- .orElseGet(() -> displayId -> false);
return Optional.of(
new CompatUIController(
context,
@@ -300,7 +296,7 @@ public abstract class WMShellBaseModule {
compatUIShellCommandHandler.get(),
accessibilityManager.get(),
compatUIStatusManager,
- inDesktopModePredicate));
+ desktopUserRepositories));
}
@WMSingleton
@@ -704,14 +700,14 @@ public abstract class WMShellBaseModule {
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellController,
shellCommandHandler, taskStackListener, activityTaskManager,
- desktopRepository, taskStackTransitionObserver, mainExecutor));
+ desktopUserRepositories, taskStackTransitionObserver, mainExecutor));
}
@BindsOptionalOf
@@ -867,6 +863,12 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
+ @WMSingleton
+ @Provides
+ static SplitState provideSplitState() {
+ return new SplitState();
+ }
+
//
// Starting window
//
@@ -1002,16 +1004,16 @@ public abstract class WMShellBaseModule {
@BindsOptionalOf
@DynamicOverride
- abstract DesktopRepository optionalDesktopRepository();
+ abstract DesktopUserRepositories optionalDesktopUserRepositories();
@WMSingleton
@Provides
- static Optional<DesktopRepository> provideDesktopRepository(Context context,
- @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
+ static Optional<DesktopUserRepositories> provideDesktopUserRepositories(Context context,
+ @DynamicOverride Optional<Lazy<DesktopUserRepositories>> desktopUserRepositories) {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopRepository.flatMap((lazy) -> {
+ return desktopUserRepositories.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index c5644a8f6876..d7ddbdeaa6da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.dagger;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import android.content.Context;
@@ -205,13 +206,14 @@ public abstract class WMShellConcurrencyModule {
}
/**
- * Provides a Shell background thread Executor for low priority background tasks.
+ * Provides a Shell background thread Executor for low priority background tasks. The thread
+ * may also be boosted to THREAD_PRIORITY_FOREGROUND if necessary.
*/
@WMSingleton
@Provides
@ShellBackgroundThread
public static ShellExecutor provideSharedBackgroundExecutor(
@ShellBackgroundThread Handler handler) {
- return new HandlerExecutor(handler);
+ return new HandlerExecutor(handler, THREAD_PRIORITY_BACKGROUND, THREAD_PRIORITY_FOREGROUND);
}
}
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 974535385334..f9e3be9c770f 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
@@ -50,6 +51,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -68,10 +70,9 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
-import com.android.wm.shell.compatui.letterbox.LetterboxController;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
-import com.android.wm.shell.compatui.letterbox.SingleSurfaceLetterboxController;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -86,11 +87,11 @@ 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.DesktopModeUiEventLogger;
-import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
@@ -150,6 +151,10 @@ 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.viewhost.DefaultWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
@@ -175,7 +180,8 @@ import java.util.Optional;
* <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(includes = {WMShellBaseModule.class, PipModule.class, ShellBackAnimationModule.class})
+@Module(includes = {WMShellBaseModule.class, PipModule.class, ShellBackAnimationModule.class,
+ LetterboxModule.class})
public abstract class WMShellModule {
//
@@ -297,6 +303,7 @@ public abstract class WMShellModule {
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel) {
if (desktopModeWindowDecorViewModel.isPresent()) {
return desktopModeWindowDecorViewModel.get();
@@ -314,7 +321,8 @@ public abstract class WMShellModule {
rootTaskDisplayAreaOrganizer,
syncQueue,
transitions,
- focusTransitionObserver);
+ focusTransitionObserver,
+ windowDecorViewHostSupplier);
}
@WMSingleton
@@ -337,6 +345,18 @@ public abstract class WMShellModule {
return new AdditionalSystemViewContainer.Factory();
}
+ @WMSingleton
+ @Provides
+ static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier(
+ @NonNull Context context,
+ @ShellMainThread @NonNull CoroutineScope mainScope) {
+ final int poolSize = DesktopModeStatus.getWindowDecorScvhPoolSize(context);
+ if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) && poolSize > 0) {
+ return new PooledWindowDecorViewHostSupplier(mainScope, poolSize);
+ }
+ return new DefaultWindowDecorViewHostSupplier(mainScope);
+ }
+
//
// Freeform
//
@@ -362,7 +382,7 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel,
@@ -374,7 +394,7 @@ public abstract class WMShellModule {
context,
init,
shellTaskOrganizer,
- desktopRepository,
+ desktopUserRepositories,
desktopTasksController,
launchAdjacentController,
windowDecorViewModel,
@@ -492,6 +512,7 @@ public abstract class WMShellModule {
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new SplitScreenController(
@@ -515,6 +536,7 @@ public abstract class WMShellModule {
desktopTasksController,
null /* stageCoordinator */,
multiInstanceHelper,
+ splitState,
mainExecutor,
mainHandler);
}
@@ -690,7 +712,7 @@ public abstract class WMShellModule {
DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
Optional<DesktopImmersiveController> desktopImmersiveController,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
@@ -726,7 +748,7 @@ public abstract class WMShellModule {
toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopImmersiveController.get(),
- desktopRepository,
+ desktopUserRepositories,
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
@@ -749,7 +771,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
ReturnToDragStartAnimator returnToDragStartAnimator,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopTilingDecorViewModel(
context,
@@ -760,7 +782,7 @@ public abstract class WMShellModule {
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository,
+ desktopUserRepositories,
desktopModeEventLogger
);
}
@@ -768,10 +790,10 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static Optional<TaskChangeListener> provideDesktopTaskChangeListener(
- Context context, @DynamicOverride DesktopRepository desktopRepository) {
+ Context context, @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
if (ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue()
&& DesktopModeStatus.canEnterDesktopMode(context)) {
- return Optional.of(new DesktopTaskChangeListener(desktopRepository));
+ return Optional.of(new DesktopTaskChangeListener(desktopUserRepositories));
}
return Optional.empty();
}
@@ -781,7 +803,7 @@ public abstract class WMShellModule {
static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
Context context,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer shellTaskOrganizer,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler) {
@@ -794,7 +816,7 @@ public abstract class WMShellModule {
return Optional.of(
new DesktopTasksLimiter(
transitions,
- desktopRepository,
+ desktopUserRepositories,
shellTaskOrganizer,
maxTaskLimit,
interactionJankMonitor,
@@ -808,7 +830,7 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
ShellCommandHandler shellCommandHandler) {
@@ -817,7 +839,7 @@ public abstract class WMShellModule {
new DesktopImmersiveController(
shellInit,
transitions,
- desktopRepository,
+ desktopUserRepositories,
displayController,
shellTaskOrganizer,
shellCommandHandler));
@@ -840,7 +862,8 @@ public abstract class WMShellModule {
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor) {
return (Flags.enableDesktopWindowingTransitions()
- || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue())
? new SpringDragToDesktopTransitionHandler(
context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
@@ -856,14 +879,16 @@ public abstract class WMShellModule {
InputManager inputManager,
ShellTaskOrganizer shellTaskOrganizer,
FocusTransitionObserver focusTransitionObserver,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ DisplayController displayController) {
if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
&& manageKeyGestures()
&& (Flags.enableMoveToNextDisplayShortcut()
|| Flags.enableTaskResizingKeyboardShortcuts())) {
return Optional.of(new DesktopModeKeyGestureHandler(context,
desktopModeWindowDecorViewModel, desktopTasksController,
- inputManager, shellTaskOrganizer, focusTransitionObserver, mainExecutor));
+ inputManager, shellTaskOrganizer, focusTransitionObserver,
+ mainExecutor, displayController));
}
return Optional.empty();
}
@@ -880,7 +905,7 @@ public abstract class WMShellModule {
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -892,6 +917,7 @@ public abstract class WMShellModule {
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -907,12 +933,12 @@ public abstract class WMShellModule {
}
return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler,
mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager,
- taskOrganizer, desktopRepository, displayController, shellController,
+ taskOrganizer, desktopUserRepositories, displayController, shellController,
displayInsetsController, syncQueue, transitions, desktopTasksController,
desktopImmersiveController.get(),
rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
- assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
- appHandleEducationController, appToWebEducationController,
+ assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper,
+ desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger));
}
@@ -925,7 +951,7 @@ public abstract class WMShellModule {
@ShellAnimationThread ShellExecutor animExecutor,
ShellInit shellInit,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository) {
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
if (!DesktopModeStatus.canEnterDesktopMode(context)
|| !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
|| !Flags.enableDesktopSystemDialogsTransitions()) {
@@ -934,7 +960,7 @@ public abstract class WMShellModule {
return Optional.of(
new SystemModalsTransitionHandler(
context, mainExecutor, animExecutor, shellInit, transitions,
- desktopRepository));
+ desktopUserRepositories));
}
@WMSingleton
@@ -993,16 +1019,17 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopRepository provideDesktopRepository(
+ static DesktopUserRepositories provideDesktopUserRepositories(
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
DesktopRepositoryInitializer desktopRepositoryInitializer,
- @ShellMainThread CoroutineScope mainScope
+ @ShellMainThread CoroutineScope mainScope,
+ UserManager userManager
) {
- return new DesktopRepository(context, shellInit, desktopPersistentRepository,
+ return new DesktopUserRepositories(context, shellInit, desktopPersistentRepository,
desktopRepositoryInitializer,
- mainScope);
+ mainScope, userManager);
}
@WMSingleton
@@ -1013,7 +1040,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopRepository desktopRepository) {
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopActivityOrientationChangeHandler(
@@ -1022,7 +1049,7 @@ public abstract class WMShellModule {
shellTaskOrganizer,
taskStackListener,
toggleResizeDesktopTaskTransitionHandler,
- desktopRepository));
+ desktopUserRepositories));
}
return Optional.empty();
}
@@ -1031,12 +1058,13 @@ public abstract class WMShellModule {
@Provides
static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
Context context,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
+ Optional<BackAnimationController> backAnimationController,
ShellInit shellInit) {
- return desktopRepository.flatMap(
+ return desktopUserRepositories.flatMap(
repository ->
Optional.of(
new DesktopTasksTransitionObserver(
@@ -1045,6 +1073,7 @@ public abstract class WMShellModule {
transitions,
shellTaskOrganizer,
desktopMixedTransitionHandler.get(),
+ backAnimationController.get(),
shellInit)));
}
@@ -1053,7 +1082,7 @@ public abstract class WMShellModule {
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository,
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
Optional<DesktopImmersiveController> desktopImmersiveController,
@@ -1071,7 +1100,7 @@ public abstract class WMShellModule {
new DesktopMixedTransitionHandler(
context,
transitions,
- desktopRepository,
+ desktopUserRepositories,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController.get(),
@@ -1307,22 +1336,4 @@ public abstract class WMShellModule {
return new Object();
}
- //
- // App Compat
- //
-
- @WMSingleton
- @Provides
- static LetterboxTransitionObserver provideLetterboxTransitionObserver(
- @NonNull ShellInit shellInit,
- @NonNull Transitions transitions,
- @NonNull LetterboxController letterboxController
- ) {
- return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
- }
-
- @WMSingleton
- @Binds
- abstract LetterboxController bindsLetterboxController(
- SingleSurfaceLetterboxController letterboxController);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 3cd5df3121c1..cfdfe3d52011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -42,7 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
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.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -171,7 +171,7 @@ public abstract class Pip1Module {
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
- Optional<DesktopRepository> desktopRepositoryOptional,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@@ -181,7 +181,7 @@ public abstract class Pip1Module {
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, pipPerfHintControllerOptional,
- desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
+ desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer, displayController,
pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
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 3508ecee6d51..3a9961917f79 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.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -38,6 +39,7 @@ import com.android.wm.shell.common.pip.PipUtils;
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.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
@@ -128,8 +130,11 @@ public abstract class Pip2Module {
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
- PipTransitionState pipTransitionState) {
- return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
+ PipTransitionState pipTransitionState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
+ desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index a16c15dfdf1a..9b5a28916148 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -106,13 +106,13 @@ constructor(
// Scale the end bounds of the window down with an anchor in the center
inset(
(startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
- (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt()
+ (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
)
val offsetY =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
CLOSE_ANIM_OFFSET_Y,
- context.resources.displayMetrics
+ context.resources.displayMetrics,
)
.toInt()
offset(/* dx= */ 0, offsetY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 606aa6cd3353..6104e79efc66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val taskStackListener: TaskStackListenerImpl,
private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
- private val taskRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
) {
init {
@@ -49,15 +49,17 @@ class DesktopActivityOrientationChangeHandler(
}
private fun onInit() {
- taskStackListener.addListener(object : TaskStackListenerCallback {
- override fun onActivityRequestedOrientationChanged(
- taskId: Int,
- @ScreenOrientation requestedOrientation: Int
- ) {
- // Handle requested screen orientation changes at runtime.
- handleActivityOrientationChange(taskId, requestedOrientation)
+ taskStackListener.addListener(
+ object : TaskStackListenerCallback {
+ override fun onActivityRequestedOrientationChanged(
+ taskId: Int,
+ @ScreenOrientation requestedOrientation: Int,
+ ) {
+ // Handle requested screen orientation changes at runtime.
+ handleActivityOrientationChange(taskId, requestedOrientation)
+ }
}
- })
+ )
}
/**
@@ -77,11 +79,13 @@ class DesktopActivityOrientationChangeHandler(
private fun handleActivityOrientationChange(
taskId: Int,
- @ScreenOrientation requestedOrientation: Int
+ @ScreenOrientation requestedOrientation: Int,
) {
if (!Flags.respectOrientationChangeForUnresizeable()) return
val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return
- if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return
+ val taskRepository = desktopUserRepositories.current
+ val isDesktopModeShowing = taskRepository.getVisibleTaskCount(task.displayId) > 0
+ if (!isDesktopModeShowing || !task.isFreeform || task.isResizeable) return
val taskBounds = task.configuration.windowConfiguration.bounds
val taskHeight = taskBounds.height()
@@ -91,10 +95,12 @@ class DesktopActivityOrientationChangeHandler(
if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT
// Non-resizeable activity requested opposite orientation.
- if (orientation == ORIENTATION_PORTRAIT
- && ActivityInfo.isFixedOrientationLandscape(requestedOrientation)
- || orientation == ORIENTATION_LANDSCAPE
- && ActivityInfo.isFixedOrientationPortrait(requestedOrientation)) {
+ if (
+ orientation == ORIENTATION_PORTRAIT &&
+ ActivityInfo.isFixedOrientationLandscape(requestedOrientation) ||
+ orientation == ORIENTATION_LANDSCAPE &&
+ ActivityInfo.isFixedOrientationPortrait(requestedOrientation)
+ ) {
val finalSize = Size(taskHeight, taskWidth)
// Use the center x as the resizing anchor point.
@@ -106,7 +112,4 @@ class DesktopActivityOrientationChangeHandler(
resizeHandler.startTransition(wct)
}
}
-
- private fun isDesktopModeShowing(displayId: Int): Boolean =
- taskRepository.getVisibleTaskCount(displayId) > 0
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
index 83b0f8413a28..56c50ff484d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
@@ -71,8 +71,7 @@ class DesktopBackNavigationTransitionHandler(
animations +=
info.changes
.filter {
- it.mode == info.type &&
- it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
}
.mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
@@ -83,7 +82,7 @@ class DesktopBackNavigationTransitionHandler(
private fun createMinimizeAnimation(
change: TransitionInfo.Change,
finishTransaction: Transaction,
- onAnimFinish: (Animator) -> Unit
+ onAnimFinish: (Animator) -> Unit,
): Animator? {
val t = Transaction()
val sc = change.leash
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 ba383fac8b47..43e8d2a30930 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
@@ -12,7 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS 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
@@ -64,16 +64,22 @@ class DesktopDisplayEventHandler(
private fun refreshDisplayWindowingMode() {
// TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
- context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
- )
+ val isExtendedDisplayEnabled =
+ 0 !=
+ Settings.Global.getInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ 0,
+ )
if (!isExtendedDisplayEnabled) {
// No action needed in mirror or projected mode.
return
}
- val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
- .any { displayId -> displayId != DEFAULT_DISPLAY }
+ val hasNonDefaultDisplay =
+ rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
+ displayId != DEFAULT_DISPLAY
+ }
val targetDisplayWindowingMode =
if (hasNonDefaultDisplay) {
WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index dd95273dd4f3..8e2a412764eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -43,14 +43,14 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
/**
- * A controller to move tasks in/out of desktop's full immersive state where the task
- * remains freeform while being able to take fullscreen bounds and have its App Header visibility
- * be transient below the status bar like in fullscreen immersive mode.
+ * A controller to move tasks in/out of desktop's full immersive state where the task remains
+ * freeform while being able to take fullscreen bounds and have its App Header visibility be
+ * transient below the status bar like in fullscreen immersive mode.
*/
class DesktopImmersiveController(
shellInit: ShellInit,
private val transitions: Transitions,
- private val desktopRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val shellCommandHandler: ShellCommandHandler,
@@ -60,25 +60,23 @@ class DesktopImmersiveController(
constructor(
shellInit: ShellInit,
transitions: Transitions,
- desktopRepository: DesktopRepository,
+ desktopUserRepositories: DesktopUserRepositories,
displayController: DisplayController,
shellTaskOrganizer: ShellTaskOrganizer,
shellCommandHandler: ShellCommandHandler,
) : this(
shellInit,
transitions,
- desktopRepository,
+ desktopUserRepositories,
displayController,
shellTaskOrganizer,
shellCommandHandler,
- { SurfaceControl.Transaction() }
+ { SurfaceControl.Transaction() },
)
- @VisibleForTesting
- var state: TransitionState? = null
+ @VisibleForTesting var state: TransitionState? = null
- @VisibleForTesting
- val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
+ @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
/** Whether there is an immersive transition that hasn't completed yet. */
private val inProgress: Boolean
@@ -103,21 +101,20 @@ class DesktopImmersiveController(
if (inProgress) {
logV(
"Cannot start entry because transition(s) already in progress: %s",
- getRunningTransitions()
+ getRunningTransitions(),
)
return
}
- val wct = WindowContainerTransaction().apply {
- setBounds(taskInfo.token, Rect())
- }
+ val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) }
logV("Moving task ${taskInfo.taskId} into immersive mode")
val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
- state = TransitionState(
- transition = transition,
- displayId = taskInfo.displayId,
- taskId = taskInfo.taskId,
- direction = Direction.ENTER
- )
+ state =
+ TransitionState(
+ transition = transition,
+ displayId = taskInfo.displayId,
+ taskId = taskInfo.taskId,
+ direction = Direction.ENTER,
+ )
}
/** Starts a transition to move an immersive task out of immersive. */
@@ -126,22 +123,24 @@ class DesktopImmersiveController(
if (inProgress) {
logV(
"Cannot start exit because transition(s) already in progress: %s",
- getRunningTransitions()
+ getRunningTransitions(),
)
return
}
- val wct = WindowContainerTransaction().apply {
- setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
- }
+ val wct =
+ WindowContainerTransaction().apply {
+ setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
+ }
logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason)
val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
- state = TransitionState(
- transition = transition,
- displayId = taskInfo.displayId,
- taskId = taskInfo.taskId,
- direction = Direction.EXIT
- )
+ state =
+ TransitionState(
+ transition = transition,
+ displayId = taskInfo.displayId,
+ taskId = taskInfo.taskId,
+ direction = Direction.EXIT,
+ )
}
/**
@@ -177,23 +176,26 @@ class DesktopImmersiveController(
reason: ExitReason,
): ExitResult {
if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
- val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId)
- ?: return ExitResult.NoExit
+ val immersiveTask =
+ desktopUserRepositories.current.getTaskInFullImmersiveState(displayId)
+ ?: return ExitResult.NoExit
if (immersiveTask == excludeTaskId) {
return ExitResult.NoExit
}
- val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask)
- ?: return ExitResult.NoExit
+ val taskInfo =
+ shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return ExitResult.NoExit
logV(
"Appending immersive exit for task: %d in display: %d for reason: %s",
- immersiveTask, displayId, reason
+ immersiveTask,
+ displayId,
+ reason,
)
wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
return ExitResult.Exit(
exitingTask = immersiveTask,
runOnTransitionStart = { transition ->
addPendingImmersiveExit(immersiveTask, displayId, transition)
- }
+ },
)
}
@@ -210,7 +212,7 @@ class DesktopImmersiveController(
reason: ExitReason,
): ExitResult {
if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
- if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
+ if (desktopUserRepositories.current.isTaskInFullImmersiveState(taskInfo.taskId)) {
// A full immersive task is being minimized, make sure the immersive state is broken
// (i.e. resize back to max bounds).
wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
@@ -221,20 +223,16 @@ class DesktopImmersiveController(
addPendingImmersiveExit(
taskId = taskInfo.taskId,
displayId = taskInfo.displayId,
- transition = transition
+ transition = transition,
)
- }
+ },
)
}
return ExitResult.NoExit
}
-
/** Whether the [change] in the [transition] is a known immersive change. */
- fun isImmersiveChange(
- transition: IBinder,
- change: TransitionInfo.Change,
- ): Boolean {
+ fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean {
return pendingExternalExitTransitions.any {
it.transition == transition && it.taskId == change.taskInfo?.taskId
}
@@ -242,11 +240,7 @@ class DesktopImmersiveController(
private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) {
pendingExternalExitTransitions.add(
- ExternalPendingExit(
- taskId = taskId,
- displayId = displayId,
- transition = transition
- )
+ ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition)
)
}
@@ -255,7 +249,7 @@ class DesktopImmersiveController(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
val state = requireState()
check(state.transition == transition) {
@@ -283,10 +277,11 @@ class DesktopImmersiveController(
finishCallback: Transitions.TransitionFinishCallback,
) {
logD("animateResize for task#%d", targetTaskId)
- val change = info.changes.firstOrNull { c ->
- val taskInfo = c.taskInfo
- return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
- }
+ val change =
+ info.changes.firstOrNull { c ->
+ val taskInfo = c.taskInfo
+ return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+ }
if (change == null) {
logD("Did not find change for task#%d to animate", targetTaskId)
startTransaction.apply()
@@ -297,9 +292,9 @@ class DesktopImmersiveController(
}
/**
- * Animate an immersive change.
+ * Animate an immersive change.
*
- * As of now, both enter and exit transitions have the same animation, a veiled resize.
+ * As of now, both enter and exit transitions have the same animation, a veiled resize.
*/
fun animateResizeChange(
change: TransitionInfo.Change,
@@ -317,8 +312,7 @@ class DesktopImmersiveController(
.setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat())
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
- onTaskResizeAnimationListener
- ?.onAnimationStart(taskId, startTransaction, startBounds)
+ onTaskResizeAnimationListener?.onAnimationStart(taskId, startTransaction, startBounds)
?: startTransaction.apply()
val updateTransaction = transactionSupplier()
ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply {
@@ -340,8 +334,7 @@ class DesktopImmersiveController(
.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.apply()
- onTaskResizeAnimationListener
- ?.onBoundsChange(taskId, updateTransaction, rect)
+ onTaskResizeAnimationListener?.onBoundsChange(taskId, updateTransaction, rect)
?: updateTransaction.apply()
}
start()
@@ -350,13 +343,13 @@ class DesktopImmersiveController(
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? = null
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
- finishTransaction: SurfaceControl.Transaction?
+ finishTransaction: SurfaceControl.Transaction?,
) {
val state = this.state ?: return
if (transition == state.transition && aborted) {
@@ -377,9 +370,12 @@ class DesktopImmersiveController(
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
) {
+ val desktopRepository: DesktopRepository = desktopUserRepositories.current
// Check if this is a pending external exit transition.
- val pendingExit = pendingExternalExitTransitions
- .firstOrNull { pendingExit -> pendingExit.transition == transition }
+ val pendingExit =
+ pendingExternalExitTransitions.firstOrNull { pendingExit ->
+ pendingExit.transition == transition
+ }
if (pendingExit != null) {
if (info.hasTaskChange(taskId = pendingExit.taskId)) {
if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) {
@@ -387,7 +383,7 @@ class DesktopImmersiveController(
desktopRepository.setTaskInFullImmersiveState(
displayId = pendingExit.displayId,
taskId = pendingExit.taskId,
- immersive = false
+ immersive = false,
)
if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId)
@@ -400,24 +396,25 @@ class DesktopImmersiveController(
// Check if this is a direct immersive enter/exit transition.
if (transition == state?.transition) {
val state = requireState()
- val immersiveChange = info.changes.firstOrNull { c ->
- c.taskInfo?.taskId == state.taskId
- }
+ val immersiveChange =
+ info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId }
if (immersiveChange == null) {
logV(
"Direct move for task#%d in %s direction missing immersive change.",
- state.taskId, state.direction
+ state.taskId,
+ state.direction,
)
return
}
val startBounds = immersiveChange.startAbsBounds
logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
+
when (state.direction) {
Direction.ENTER -> {
desktopRepository.setTaskInFullImmersiveState(
displayId = state.displayId,
taskId = state.taskId,
- immersive = true
+ immersive = true,
)
if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds)
@@ -427,7 +424,7 @@ class DesktopImmersiveController(
desktopRepository.setTaskInFullImmersiveState(
displayId = state.displayId,
taskId = state.taskId,
- immersive = false
+ immersive = false,
)
if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
desktopRepository.removeBoundsBeforeFullImmersive(state.taskId)
@@ -447,31 +444,34 @@ class DesktopImmersiveController(
desktopRepository.setTaskInFullImmersiveState(
displayId = c.taskInfo!!.displayId,
taskId = c.taskInfo!!.taskId,
- immersive = false
+ immersive = false,
)
}
}
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
- val pendingExit = pendingExternalExitTransitions
- .firstOrNull { pendingExit -> pendingExit.transition == merged }
+ val pendingExit =
+ pendingExternalExitTransitions.firstOrNull { pendingExit ->
+ pendingExit.transition == merged
+ }
if (pendingExit != null) {
logV(
"Pending exit transition %s for task#%s merged into %s",
- merged, pendingExit.taskId, playing
+ merged,
+ pendingExit.taskId,
+ playing,
)
pendingExit.transition = playing
}
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- val pendingExit = pendingExternalExitTransitions
- .firstOrNull { pendingExit -> pendingExit.transition == transition }
+ val pendingExit =
+ pendingExternalExitTransitions.firstOrNull { pendingExit ->
+ pendingExit.transition == transition
+ }
if (pendingExit != null) {
- logV(
- "Pending exit transition %s for task#%s finished",
- transition, pendingExit
- )
+ logV("Pending exit transition %s for task#%s finished", transition, pendingExit)
pendingExternalExitTransitions.remove(pendingExit)
}
}
@@ -481,10 +481,11 @@ class DesktopImmersiveController(
}
private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
- ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}")
+ val displayLayout =
+ displayController.getDisplayLayout(taskInfo.displayId)
+ ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}")
return if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
- desktopRepository.removeBoundsBeforeFullImmersive(taskInfo.taskId)
+ desktopUserRepositories.current.removeBoundsBeforeFullImmersive(taskInfo.taskId)
?: if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
calculateInitialBounds(displayLayout, taskInfo)
} else {
@@ -500,12 +501,8 @@ class DesktopImmersiveController(
private fun getRunningTransitions(): List<IBinder> {
val running = mutableListOf<IBinder>()
- state?.let {
- running.add(it.transition)
- }
- pendingExternalExitTransitions.forEach {
- running.add(it.transition)
- }
+ state?.let { running.add(it.transition) }
+ pendingExternalExitTransitions.forEach { running.add(it.transition) }
return running
}
@@ -525,28 +522,23 @@ class DesktopImmersiveController(
val transition: IBinder,
val displayId: Int,
val taskId: Int,
- val direction: Direction
+ val direction: Direction,
)
/**
* Tracks state of a transition involving an immersive exit that is external to this class' own
- * transitions. This usually means transitions that exit immersive mode as a side-effect and
- * not the primary action (for example, minimizing the immersive task or launching a new task
- * on top of the immersive task).
+ * transitions. This usually means transitions that exit immersive mode as a side-effect and not
+ * the primary action (for example, minimizing the immersive task or launching a new task on top
+ * of the immersive task).
*/
- data class ExternalPendingExit(
- val taskId: Int,
- val displayId: Int,
- var transition: IBinder,
- )
+ data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder)
/** The result of an external exit request. */
sealed class ExitResult {
/** An immersive task exit (meaning, resize) was appended to the request. */
- data class Exit(
- val exitingTask: Int,
- val runOnTransitionStart: ((IBinder) -> Unit)
- ) : ExitResult()
+ data class Exit(val exitingTask: Int, val runOnTransitionStart: ((IBinder) -> Unit)) :
+ ExitResult()
+
/** There was no exit appended to the request. */
data object NoExit : ExitResult()
@@ -556,7 +548,8 @@ class DesktopImmersiveController(
@VisibleForTesting
enum class Direction {
- ENTER, EXIT
+ ENTER,
+ EXIT,
}
/** The reason for moving the task out of desktop immersive mode. */
@@ -579,7 +572,6 @@ class DesktopImmersiveController(
companion object {
private const val TAG = "DesktopImmersive"
- @VisibleForTesting
- const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+ @VisibleForTesting const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
}
}
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 82c2ebc7ec77..50187d552b09 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
@@ -23,6 +23,7 @@ import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.window.DesktopModeFlags
import android.window.TransitionInfo
@@ -49,7 +50,7 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
class DesktopMixedTransitionHandler(
private val context: Context,
private val transitions: Transitions,
- private val desktopRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
@@ -61,11 +62,10 @@ class DesktopMixedTransitionHandler(
) : MixedTransitionHandler, FreeformTaskTransitionStarter {
init {
- shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
+ shellInit.addInitCallback({ transitions.addHandler(this) }, this)
}
- @VisibleForTesting
- val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
+ @VisibleForTesting val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
/** Delegates starting transition to [FreeformTaskTransitionHandler]. */
override fun startWindowingModeTransition(
@@ -77,13 +77,21 @@ class DesktopMixedTransitionHandler(
override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder =
freeformTaskTransitionHandler.startMinimizedModeTransition(wct)
+ /** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */
+ override fun startPipTransition(wct: WindowContainerTransaction?): IBinder =
+ freeformTaskTransitionHandler.startPipTransition(wct)
+
/** Starts close transition and handles or delegates desktop task close animation. */
override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue
+ ) {
return freeformTaskTransitionHandler.startRemoveTransition(wct)
}
requireNotNull(wct)
- return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+ return transitions
+ .startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
.also { transition ->
pendingMixedTransitions.add(PendingMixedTransition.Close(transition))
}
@@ -100,8 +108,11 @@ class DesktopMixedTransitionHandler(
minimizingTaskId: Int? = null,
exitingImmersiveTask: Int? = null,
): IBinder {
- if (!Flags.enableFullyImmersiveInDesktop() &&
- !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ !Flags.enableFullyImmersiveInDesktop() &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
return transitions.startTransition(transitionType, wct, /* handler= */ null)
}
if (exitingImmersiveTask == null) {
@@ -109,18 +120,21 @@ class DesktopMixedTransitionHandler(
} else {
logV(
"Starting mixed launch transition for task#%d with immersive exit of task#%d",
- taskId, exitingImmersiveTask
+ taskId,
+ exitingImmersiveTask,
)
}
- return transitions.startTransition(transitionType, wct, /* handler= */ this)
- .also { transition ->
- pendingMixedTransitions.add(PendingMixedTransition.Launch(
+ return transitions.startTransition(transitionType, wct, /* handler= */ this).also {
+ transition ->
+ pendingMixedTransitions.add(
+ PendingMixedTransition.Launch(
transition = transition,
launchingTask = taskId,
minimizingTask = minimizingTaskId,
exitingImmersiveTask = exitingImmersiveTask,
- ))
- }
+ )
+ )
+ }
}
/** Notifies this handler that there is a pending transition for it to handle. */
@@ -141,36 +155,38 @@ class DesktopMixedTransitionHandler(
finishTransaction: SurfaceControl.Transaction,
finishCallback: TransitionFinishCallback,
): Boolean {
- val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
- ?: return false.also {
- logV("No pending desktop transition")
- }
+ val pending =
+ pendingMixedTransitions.find { pending -> pending.transition == transition }
+ ?: return false.also { logV("No pending desktop transition") }
pendingMixedTransitions.remove(pending)
logV("Animating pending mixed transition: %s", pending)
return when (pending) {
- is PendingMixedTransition.Close -> animateCloseTransition(
- transition,
- info,
- startTransaction,
- finishTransaction,
- finishCallback
- )
- is PendingMixedTransition.Launch -> animateLaunchTransition(
- pending,
- transition,
- info,
- startTransaction,
- finishTransaction,
- finishCallback
- )
- is PendingMixedTransition.Minimize -> animateMinimizeTransition(
- pending,
- transition,
- info,
- startTransaction,
- finishTransaction,
- finishCallback
- )
+ is PendingMixedTransition.Close ->
+ animateCloseTransition(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ is PendingMixedTransition.Launch ->
+ animateLaunchTransition(
+ pending,
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ is PendingMixedTransition.Minimize ->
+ animateMinimizeTransition(
+ pending,
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
}
}
@@ -186,8 +202,9 @@ class DesktopMixedTransitionHandler(
logW("Should have closing desktop task")
return false
}
- if (isLastDesktopTask(closeChange)) {
- // Dispatch close desktop task animation to the default transition handlers.
+ if (isWallpaperActivityClosing(info)) {
+ // If the wallpaper activity is closing then the desktop is closing, animate the closing
+ // desktop by dispatching to other transition handlers.
return dispatchCloseLastDesktopTaskAnimation(
transition,
info,
@@ -216,12 +233,10 @@ class DesktopMixedTransitionHandler(
finishCallback: TransitionFinishCallback,
): Boolean {
// Check if there's also an immersive change during this launch.
- val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
- findTaskChange(info, exitingTask)
- }
- val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
- findTaskChange(info, minimizingTask)
- }
+ val immersiveExitChange =
+ pending.exitingImmersiveTask?.let { exitingTask -> findTaskChange(info, exitingTask) }
+ val minimizeChange =
+ pending.minimizingTask?.let { minimizingTask -> findTaskChange(info, minimizingTask) }
val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask)
if (launchChange == null) {
check(minimizeChange == null)
@@ -241,10 +256,14 @@ class DesktopMixedTransitionHandler(
logV(
"Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
- launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
- immersiveExitChange?.taskInfo?.taskId
+ launchChange.taskInfo!!.taskId,
+ minimizeChange?.taskInfo?.taskId,
+ immersiveExitChange?.taskInfo?.taskId,
)
- if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
// Only apply minimize change reparenting here if we implement the new app launch
// transitions, otherwise this reparenting is handled in the default handler.
minimizeChange?.let {
@@ -259,7 +278,7 @@ class DesktopMixedTransitionHandler(
immersiveExitChange,
startTransaction,
finishTransaction,
- finishCb
+ finishCb,
)
// Let the leftover/default handler animate the remaining changes.
return dispatchToLeftoverHandler(
@@ -267,7 +286,7 @@ class DesktopMixedTransitionHandler(
info,
startTransaction,
finishTransaction,
- finishCb
+ finishCb,
)
}
// There's nothing to animate separately, so let the left over handler animate
@@ -278,7 +297,7 @@ class DesktopMixedTransitionHandler(
info,
startTransaction,
finishTransaction,
- finishCb
+ finishCb,
)
}
@@ -304,7 +323,7 @@ class DesktopMixedTransitionHandler(
info,
startTransaction,
finishTransaction,
- finishCallback
+ finishCallback,
)
}
@@ -321,7 +340,7 @@ class DesktopMixedTransitionHandler(
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
- finishTransaction: SurfaceControl.Transaction?
+ finishTransaction: SurfaceControl.Transaction?,
) {
pendingMixedTransitions.removeAll { pending -> pending.transition == transition }
super.onTransitionConsumed(transition, aborted, finishTransaction)
@@ -356,7 +375,7 @@ class DesktopMixedTransitionHandler(
doOnFinishCallback = {
// Finish the jank trace when closing the last window in desktop mode.
interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
- }
+ },
)
}
@@ -379,7 +398,10 @@ class DesktopMixedTransitionHandler(
require(taskInfo.isFreeform)
logV("Reparenting minimizing task#%d", taskInfo.taskId)
rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
- taskInfo.displayId, minimizeChange.leash, startTransaction)
+ taskInfo.displayId,
+ minimizeChange.leash,
+ startTransaction,
+ )
}
private fun dispatchToLeftoverHandler(
@@ -399,14 +421,16 @@ class DesktopMixedTransitionHandler(
doOnFinishCallback?.invoke()
finishCallback.onTransitionFinished(wct)
},
- /* skip= */ this
+ /* skip= */ this,
) != null
}
- private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
- change.taskInfo?.let {
- desktopRepository.getExpandedTaskCount(it.displayId) == 1
- } ?: false
+ private fun isWallpaperActivityClosing(info: TransitionInfo) =
+ info.changes.any { change ->
+ change.mode == TRANSIT_CLOSE &&
+ change.taskInfo != null &&
+ DesktopWallpaperActivity.isWallpaperTask(change.taskInfo!!)
+ }
private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
if (info.type != WindowManager.TRANSIT_CLOSE) return null
@@ -423,7 +447,7 @@ class DesktopMixedTransitionHandler(
private fun findDesktopTaskLaunchChange(
info: TransitionInfo,
- launchTaskId: Int?
+ launchTaskId: Int?,
): TransitionInfo.Change? {
return if (launchTaskId != null) {
// Launching a known task (probably from background or moving to front), so
@@ -432,8 +456,9 @@ class DesktopMixedTransitionHandler(
} 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
+ change.mode == TRANSIT_OPEN &&
+ change.taskInfo != null &&
+ change.taskInfo!!.isFreeform
}
}
}
@@ -451,9 +476,7 @@ class DesktopMixedTransitionHandler(
abstract val transition: IBinder
/** A task is closing. */
- data class Close(
- override val transition: IBinder,
- ) : PendingMixedTransition()
+ data class Close(override val transition: IBinder) : PendingMixedTransition()
/** A task is opening or moving to front. */
data class Launch(
@@ -463,8 +486,10 @@ class DesktopMixedTransitionHandler(
val exitingImmersiveTask: Int?,
) : PendingMixedTransition()
- /** A task is minimizing. This should be used for task going to back and some closing cases
- * with back navigation. */
+ /**
+ * A task is minimizing. This should be used for task going to back and some closing cases
+ * with back navigation.
+ */
data class Minimize(
override val transition: IBinder,
val minimizingTask: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
index a7a4a1036b5d..ca02c72c174e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
@@ -27,16 +27,14 @@ import android.window.WindowContainerTransaction
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
-/**
- * Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode.
- */
+/** Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode. */
class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitions) :
Transitions.TransitionHandler {
private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf()
/**
- * Begin a transition when a [android.app.PendingIntent] is dropped without a window to
- * accept it.
+ * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept
+ * it.
*/
fun handleDropEvent(wct: WindowContainerTransaction): IBinder {
val token = transitions.startTransition(TRANSIT_OPEN, wct, this)
@@ -49,29 +47,32 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: TransitionFinishCallback
+ finishCallback: TransitionFinishCallback,
): Boolean {
if (!pendingTransitionTokens.contains(transition)) return false
val change = findRelevantChange(info)
val leash = change.leash
val endBounds = change.endAbsBounds
- startTransaction.hide(leash)
+ startTransaction
+ .hide(leash)
.setWindowCrop(leash, endBounds.width(), endBounds.height())
.apply()
val animator = ValueAnimator()
animator.setFloatValues(0f, 1f)
animator.setDuration(FADE_IN_ANIMATION_DURATION)
val t = SurfaceControl.Transaction()
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- t.show(leash)
- t.apply()
- }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ t.show(leash)
+ t.apply()
+ }
- override fun onAnimationEnd(animation: Animator) {
- finishCallback.onTransitionFinished(null)
+ override fun onAnimationEnd(animation: Animator) {
+ finishCallback.onTransitionFinished(null)
+ }
}
- })
+ )
animator.addUpdateListener { animation: ValueAnimator ->
t.setAlpha(leash, animation.animatedFraction)
t.apply()
@@ -83,9 +84,7 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio
private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
val matchingChanges =
- info.changes.filter { c ->
- isValidTaskChange(c) && c.mode == TRANSIT_OPEN
- }
+ info.changes.filter { c -> isValidTaskChange(c) && c.mode == TRANSIT_OPEN }
if (matchingChanges.size != 1) {
throw IllegalStateException(
"Expected 1 relevant change but found: ${matchingChanges.size}"
@@ -100,7 +99,7 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? {
return null
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index dc23128b7b2a..ff6fb59d6494 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -35,29 +35,25 @@ import java.security.SecureRandom
import java.util.Random
import java.util.concurrent.atomic.AtomicInteger
-
/** Event logger for logging desktop mode session events */
class DesktopModeEventLogger {
private val random: Random = SecureRandom()
/** The session id for the current desktop mode session */
- @VisibleForTesting
- val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID)
+ @VisibleForTesting val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID)
private fun generateSessionId() = 1 + random.nextInt(1 shl 20)
- /**
- * Logs enter into desktop mode with [enterReason]
- */
+ /** Logs enter into desktop mode with [enterReason] */
fun logSessionEnter(enterReason: EnterReason) {
val sessionId = generateSessionId()
val previousSessionId = currentSessionId.getAndSet(sessionId)
if (previousSessionId != NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: Existing desktop mode session id: %s found on desktop "
- + "mode enter",
- previousSessionId
+ "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " +
+ "mode enter",
+ previousSessionId,
)
}
@@ -65,27 +61,25 @@ class DesktopModeEventLogger {
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session enter, session: %s reason: %s",
sessionId,
- enterReason.name
+ enterReason.name,
)
FrameworkStatsLog.write(
DESKTOP_MODE_ATOM_ID,
/* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
/* enterReason */ enterReason.reason,
/* exitReason */ 0,
- /* session_id */ sessionId
+ /* session_id */ sessionId,
)
EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId)
}
- /**
- * Logs exit from desktop mode session with [exitReason]
- */
+ /** Logs exit from desktop mode session with [exitReason] */
fun logSessionExit(exitReason: ExitReason) {
val sessionId = currentSessionId.getAndSet(NO_SESSION_ID)
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging exit from desktop mode"
+ "DesktopModeLogger: No session id found for logging exit from desktop mode",
)
return
}
@@ -94,27 +88,25 @@ class DesktopModeEventLogger {
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging session exit, session: %s reason: %s",
sessionId,
- exitReason.name
+ exitReason.name,
)
FrameworkStatsLog.write(
DESKTOP_MODE_ATOM_ID,
/* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
/* enterReason */ 0,
/* exitReason */ exitReason.reason,
- /* session_id */ sessionId
+ /* session_id */ sessionId,
)
EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId)
}
- /**
- * Logs that a task with [taskUpdate] was added in a desktop mode session
- */
+ /** Logs that a task with [taskUpdate] was added in a desktop mode session */
fun logTaskAdded(taskUpdate: TaskUpdate) {
val sessionId = currentSessionId.get()
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging task added"
+ "DesktopModeLogger: No session id found for logging task added",
)
return
}
@@ -123,23 +115,22 @@ class DesktopModeEventLogger {
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task added, session: %s taskId: %s",
sessionId,
- taskUpdate.instanceId
+ taskUpdate.instanceId,
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
- sessionId, taskUpdate
+ sessionId,
+ taskUpdate,
)
}
- /**
- * Logs that a task with [taskUpdate] was removed from a desktop mode session
- */
+ /** Logs that a task with [taskUpdate] was removed from a desktop mode session */
fun logTaskRemoved(taskUpdate: TaskUpdate) {
val sessionId = currentSessionId.get()
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging task removed"
+ "DesktopModeLogger: No session id found for logging task removed",
)
return
}
@@ -148,23 +139,22 @@ class DesktopModeEventLogger {
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task remove, session: %s taskId: %s",
sessionId,
- taskUpdate.instanceId
+ taskUpdate.instanceId,
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
- sessionId, taskUpdate
+ sessionId,
+ taskUpdate,
)
}
- /**
- * Logs that a task with [taskUpdate] had it's info changed in a desktop mode session
- */
+ /** Logs that a task with [taskUpdate] had it's info changed in a desktop mode session */
fun logTaskInfoChanged(taskUpdate: TaskUpdate) {
val sessionId = currentSessionId.get()
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging task info changed"
+ "DesktopModeLogger: No session id found for logging task info changed",
)
return
}
@@ -173,11 +163,12 @@ class DesktopModeEventLogger {
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
sessionId,
- taskUpdate.instanceId
+ taskUpdate.instanceId,
)
logTaskUpdate(
FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
- sessionId, taskUpdate
+ sessionId,
+ taskUpdate,
)
}
@@ -200,30 +191,32 @@ class DesktopModeEventLogger {
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging start of task resizing"
+ "DesktopModeLogger: No session id found for logging start of task resizing",
)
return
}
- val taskSizeUpdate = createTaskSizeUpdate(
- resizeTrigger,
- inputMethod,
- taskInfo,
- taskWidth,
- taskHeight,
- displayController = displayController,
- displayLayoutSize = displayLayoutSize,
- )
+ val taskSizeUpdate =
+ createTaskSizeUpdate(
+ resizeTrigger,
+ inputMethod,
+ taskInfo,
+ taskWidth,
+ taskHeight,
+ displayController = displayController,
+ displayLayoutSize = displayLayoutSize,
+ )
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task resize is starting, session: %s, taskSizeUpdate: %s",
sessionId,
- taskSizeUpdate
+ taskSizeUpdate,
)
logTaskSizeUpdated(
FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
- sessionId, taskSizeUpdate
+ sessionId,
+ taskSizeUpdate,
)
}
@@ -245,31 +238,33 @@ class DesktopModeEventLogger {
if (sessionId == NO_SESSION_ID) {
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: No session id found for logging end of task resizing"
+ "DesktopModeLogger: No session id found for logging end of task resizing",
)
return
}
- val taskSizeUpdate = createTaskSizeUpdate(
- resizeTrigger,
- inputMethod,
- taskInfo,
- taskWidth,
- taskHeight,
- displayController,
- displayLayoutSize,
- )
+ val taskSizeUpdate =
+ createTaskSizeUpdate(
+ resizeTrigger,
+ inputMethod,
+ taskInfo,
+ taskWidth,
+ taskHeight,
+ displayController,
+ displayLayoutSize,
+ )
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: Logging task resize is ending, session: %s, taskSizeUpdate: %s",
sessionId,
- taskSizeUpdate
+ taskSizeUpdate,
)
logTaskSizeUpdated(
FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
- sessionId, taskSizeUpdate
+ sessionId,
+ taskSizeUpdate,
)
}
@@ -287,12 +282,15 @@ class DesktopModeEventLogger {
val height = taskHeight ?: taskBounds.height()
val width = taskWidth ?: taskBounds.width()
- val displaySize = when {
- displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
- displayController != null -> displayController.getDisplayLayout(taskInfo.displayId)
- ?.let { it.height() * it.width() }
- else -> null
- }
+ val displaySize =
+ when {
+ displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
+ displayController != null ->
+ displayController.getDisplayLayout(taskInfo.displayId)?.let {
+ it.height() * it.width()
+ }
+ else -> null
+ }
return TaskSizeUpdate(
resizeTrigger,
@@ -316,8 +314,8 @@ class DesktopModeEventLogger {
taskHeight = 0,
taskWidth = 0,
taskX = 0,
- taskY = 0
- )
+ taskY = 0,
+ ),
)
}
@@ -343,7 +341,7 @@ class DesktopModeEventLogger {
taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
/* visible_task_count */
- taskUpdate.visibleTaskCount
+ taskUpdate.visibleTaskCount,
)
EventLogTags.writeWmShellDesktopModeTaskUpdate(
/* task_event */
@@ -365,14 +363,14 @@ class DesktopModeEventLogger {
taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
/* visible_task_count */
- taskUpdate.visibleTaskCount
+ taskUpdate.visibleTaskCount,
)
}
private fun logTaskSizeUpdated(
resizingStage: Int,
sessionId: Int,
- taskSizeUpdate: TaskSizeUpdate
+ taskSizeUpdate: TaskSizeUpdate,
) {
FrameworkStatsLog.write(
DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
@@ -393,7 +391,7 @@ class DesktopModeEventLogger {
/* task_width */
taskSizeUpdate.taskWidth,
/* display_area */
- taskSizeUpdate.displayArea ?: -1
+ taskSizeUpdate.displayArea ?: -1,
)
}
@@ -410,7 +408,6 @@ class DesktopModeEventLogger {
* @property taskY y-coordinate of the top-left corner
* @property minimizeReason the reason the task was minimized
* @property unminimizeEvent the reason the task was unminimized
- *
*/
data class TaskUpdate(
val instanceId: Int,
@@ -425,8 +422,7 @@ class DesktopModeEventLogger {
)
/**
- * Describes a task size update (resizing, snapping or maximizing to
- * stable bounds).
+ * Describes a task size update (resizing, snapping or maximizing to stable bounds).
*
* @property resizeTrigger the trigger for task resize
* @property inputMethod the input method for resizing this task
@@ -450,9 +446,7 @@ class DesktopModeEventLogger {
fun getInputMethodFromMotionEvent(e: MotionEvent?): InputMethod {
if (e == null) return InputMethod.UNKNOWN_INPUT_METHOD
- val toolType = e.getToolType(
- e.findPointerIndex(e.getPointerId(0))
- )
+ val toolType = e.getToolType(e.findPointerIndex(e.getPointerId(0)))
return when {
toolType == TOOL_TYPE_STYLUS -> InputMethod.STYLUS
toolType == TOOL_TYPE_MOUSE -> InputMethod.MOUSE
@@ -474,8 +468,7 @@ class DesktopModeEventLogger {
.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
),
MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
),
}
@@ -611,20 +604,16 @@ class DesktopModeEventLogger {
*/
enum class InputMethod(val method: Int) {
UNKNOWN_INPUT_METHOD(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
),
TOUCH(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
),
STYLUS(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
),
MOUSE(
- FrameworkStatsLog
- .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
),
TOUCHPAD(
FrameworkStatsLog
@@ -643,4 +632,4 @@ class DesktopModeEventLogger {
FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
@VisibleForTesting const val NO_SESSION_ID = 0
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 2b0724d64d0e..1ddb834399cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -16,30 +16,28 @@
package com.android.wm.shell.desktopmode
-import android.hardware.input.KeyGestureEvent
-
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventHandler
-import android.os.IBinder
-import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
-import com.android.wm.shell.ShellTaskOrganizer
import android.app.ActivityManager.RunningTaskInfo
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
-import com.android.internal.protolog.ProtoLog
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
+import android.os.IBinder
import com.android.hardware.input.Flags.manageKeyGestures
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
-import com.android.wm.shell.transition.FocusTransitionObserver
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.FocusTransitionObserver
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
import java.util.Optional
-/**
- * Handles key gesture events (keyboard shortcuts) in Desktop Mode.
- */
+/** Handles key gesture events (keyboard shortcuts) in Desktop Mode. */
class DesktopModeKeyGestureHandler(
private val context: Context,
private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>,
@@ -48,70 +46,86 @@ class DesktopModeKeyGestureHandler(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val focusTransitionObserver: FocusTransitionObserver,
@ShellMainThread private val mainExecutor: ShellExecutor,
- ) : KeyGestureEventHandler {
+ private val displayController: DisplayController,
+) : KeyGestureEventHandler {
init {
inputManager.registerKeyGestureEventHandler(this)
}
override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean {
- if (!isKeyGestureSupported(event.keyGestureType) || !desktopTasksController.isPresent
- || !desktopModeWindowDecorViewModel.isPresent) {
+ if (
+ !isKeyGestureSupported(event.keyGestureType) ||
+ !desktopTasksController.isPresent ||
+ !desktopModeWindowDecorViewModel.isPresent
+ ) {
return false
}
when (event.keyGestureType) {
KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
getGloballyFocusedFreeformTask()?.let {
- desktopTasksController.get().moveToNextDisplay(
- it.taskId
- )
+ mainExecutor.execute {
+ desktopTasksController.get().moveToNextDisplay(it.taskId)
+ }
}
return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> {
logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- desktopModeWindowDecorViewModel.get().onSnapResize(
- it.taskId,
- true,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- /* fromMenu= */ false
- )
+ mainExecutor.execute {
+ desktopModeWindowDecorViewModel
+ .get()
+ .onSnapResize(
+ it.taskId,
+ true,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false,
+ )
+ }
}
return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> {
logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- desktopModeWindowDecorViewModel.get().onSnapResize(
- it.taskId,
- false,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- /* fromMenu= */ false
- )
+ mainExecutor.execute {
+ desktopModeWindowDecorViewModel
+ .get()
+ .onSnapResize(
+ it.taskId,
+ false,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false,
+ )
+ }
}
return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
- getGloballyFocusedFreeformTask()?.let {
- desktopTasksController.get().toggleDesktopTaskSize(
- it,
- ResizeTrigger.MAXIMIZE_MENU,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- )
+ getGloballyFocusedFreeformTask()?.let { taskInfo ->
+ mainExecutor.execute {
+ desktopTasksController
+ .get()
+ .toggleDesktopTaskSize(
+ taskInfo,
+ ToggleTaskSizeInteraction(
+ isMaximized = isTaskMaximized(taskInfo, displayController),
+ source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+ inputMethod =
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ ),
+ )
+ }
}
return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- mainExecutor.execute {
- desktopTasksController.get().minimizeTask(
- it,
- )
- }
+ mainExecutor.execute { desktopTasksController.get().minimizeTask(it) }
}
return true
}
@@ -119,16 +133,17 @@ class DesktopModeKeyGestureHandler(
}
}
- override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
- -> enableMoveToNextDisplayShortcut()
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
- KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
- -> enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
- else -> false
- }
+ override fun isKeyGestureSupported(gestureType: Int): Boolean =
+ when (gestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY ->
+ enableMoveToNextDisplayShortcut()
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW ->
+ enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
+ else -> false
+ }
// TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it
// will pick a wrong task when a user quickly perform other actions with keyboard shortcuts
@@ -136,7 +151,7 @@ class DesktopModeKeyGestureHandler(
private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
shellTaskOrganizer.getRunningTasks().find { taskInfo ->
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
- focusTransitionObserver.hasGlobalFocus(taskInfo)
+ focusTransitionObserver.hasGlobalFocus(taskInfo)
}
private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 41febdfb3c2e..dfa2d9b6bb63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -60,7 +60,7 @@ class DesktopModeLoggerTransitionObserver(
context: Context,
shellInit: ShellInit,
private val transitions: Transitions,
- private val desktopModeEventLogger: DesktopModeEventLogger
+ private val desktopModeEventLogger: DesktopModeEventLogger,
) : Transitions.TransitionObserver {
init {
@@ -89,7 +89,8 @@ class DesktopModeLoggerTransitionObserver(
transitions.registerObserver(this)
SystemProperties.set(
VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
- VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)
+ VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE,
+ )
desktopModeEventLogger.logTaskInfoStateInit()
}
@@ -97,13 +98,13 @@ class DesktopModeLoggerTransitionObserver(
transition: IBinder,
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ finishTransaction: SurfaceControl.Transaction,
) {
// this was a new recents animation
if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) {
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: Recents animation running, saving tasks for later"
+ "DesktopModeLogger: Recents animation running, saving tasks for later",
)
// TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
// recents animation
@@ -129,7 +130,7 @@ class DesktopModeLoggerTransitionObserver(
) {
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopModeLogger: Canceled recents animation, restoring tasks"
+ "DesktopModeLogger: Canceled recents animation, restoring tasks",
)
// restore saved tasks in the updated set and clear for next use
postTransitionVisibleFreeformTasks += tasksSavedForRecents
@@ -140,7 +141,7 @@ class DesktopModeLoggerTransitionObserver(
identifyLogEventAndUpdateState(
transitionInfo = info,
preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
- postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+ postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks,
)
wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition()
}
@@ -200,7 +201,7 @@ class DesktopModeLoggerTransitionObserver(
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopModeLogger: taskInfo map after processing changes %s",
- postTransitionFreeformTasks.size()
+ postTransitionFreeformTasks.size(),
)
return postTransitionFreeformTasks
@@ -226,7 +227,7 @@ class DesktopModeLoggerTransitionObserver(
private fun identifyLogEventAndUpdateState(
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
- postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
) {
if (
postTransitionVisibleFreeformTasks.isEmpty() &&
@@ -236,12 +237,10 @@ class DesktopModeLoggerTransitionObserver(
// Sessions is finishing, log task updates followed by an exit event
identifyAndLogTaskUpdates(
preTransitionVisibleFreeformTasks,
- postTransitionVisibleFreeformTasks
+ postTransitionVisibleFreeformTasks,
)
- desktopModeEventLogger.logSessionExit(
- getExitReason(transitionInfo)
- )
+ desktopModeEventLogger.logSessionExit(getExitReason(transitionInfo))
isSessionActive = false
} else if (
postTransitionVisibleFreeformTasks.isNotEmpty() &&
@@ -250,19 +249,17 @@ class DesktopModeLoggerTransitionObserver(
) {
// Session is starting, log enter event followed by task updates
isSessionActive = true
- desktopModeEventLogger.logSessionEnter(
- getEnterReason(transitionInfo)
- )
+ desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
identifyAndLogTaskUpdates(
preTransitionVisibleFreeformTasks,
- postTransitionVisibleFreeformTasks
+ postTransitionVisibleFreeformTasks,
)
} else if (isSessionActive) {
// Session is neither starting, nor finishing, log task updates if there are any
identifyAndLogTaskUpdates(
preTransitionVisibleFreeformTasks,
- postTransitionVisibleFreeformTasks
+ postTransitionVisibleFreeformTasks,
)
}
@@ -274,11 +271,11 @@ class DesktopModeLoggerTransitionObserver(
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
- postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
) {
postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
- val currentTaskUpdate = buildTaskUpdateForTask(taskInfo,
- postTransitionVisibleFreeformTasks.size())
+ val currentTaskUpdate =
+ buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
when {
// new tasks added
@@ -287,16 +284,20 @@ class DesktopModeLoggerTransitionObserver(
Trace.setCounter(
Trace.TRACE_TAG_WINDOW_MANAGER,
VISIBLE_TASKS_COUNTER_NAME,
- postTransitionVisibleFreeformTasks.size().toLong()
+ postTransitionVisibleFreeformTasks.size().toLong(),
+ )
+ SystemProperties.set(
+ VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+ postTransitionVisibleFreeformTasks.size().toString(),
)
- SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
- postTransitionVisibleFreeformTasks.size().toString())
}
// old tasks that were resized or repositioned
// TODO(b/347935387): Log changes only once they are stable.
- buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size())
- != currentTaskUpdate ->
- desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate)
+ buildTaskUpdateForTask(
+ previousTaskInfo,
+ postTransitionVisibleFreeformTasks.size(),
+ ) != currentTaskUpdate ->
+ desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate)
}
}
@@ -304,14 +305,17 @@ class DesktopModeLoggerTransitionObserver(
preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
desktopModeEventLogger.logTaskRemoved(
- buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()))
+ buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
+ )
Trace.setCounter(
Trace.TRACE_TAG_WINDOW_MANAGER,
VISIBLE_TASKS_COUNTER_NAME,
- postTransitionVisibleFreeformTasks.size().toLong()
+ postTransitionVisibleFreeformTasks.size().toLong(),
+ )
+ SystemProperties.set(
+ VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+ postTransitionVisibleFreeformTasks.size().toString(),
)
- SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
- postTransitionVisibleFreeformTasks.size().toString())
}
}
}
@@ -332,45 +336,50 @@ class DesktopModeLoggerTransitionObserver(
/** Get [EnterReason] for this session enter */
private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
- val enterReason = when {
- transitionInfo.type == WindowManager.TRANSIT_WAKE
- // If there is a screen lock, desktop window entry is after dismissing keyguard
- || (transitionInfo.type == WindowManager.TRANSIT_TO_BACK
- && wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON
- transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
- EnterReason.APP_HANDLE_DRAG
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
- EnterReason.APP_HANDLE_MENU_BUTTON
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
- EnterReason.APP_FROM_OVERVIEW
- transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
- EnterReason.KEYBOARD_SHORTCUT_ENTER
- // NOTE: the below condition also applies for EnterReason quickswitch
- transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows.
- // TODO(b/346564416): Modify logging for cancelled recents once it transition is
- // changed. Also see how to account to time difference between actual enter time and
- // time of this log. Also account for the missed session when exit happens just after
- // a cancelled recents.
- wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW
- transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
- else -> {
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "Unknown enter reason for transition type: %s",
- transitionInfo.type
- )
- EnterReason.UNKNOWN_ENTER
+ val enterReason =
+ when {
+ transitionInfo.type == WindowManager.TRANSIT_WAKE
+ // If there is a screen lock, desktop window entry is after dismissing keyguard
+ ||
+ (transitionInfo.type == WindowManager.TRANSIT_TO_BACK &&
+ wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON
+ transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
+ EnterReason.APP_HANDLE_DRAG
+ transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
+ EnterReason.APP_HANDLE_MENU_BUTTON
+ transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
+ EnterReason.APP_FROM_OVERVIEW
+ transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
+ EnterReason.KEYBOARD_SHORTCUT_ENTER
+ // NOTE: the below condition also applies for EnterReason quickswitch
+ transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on
+ // the
+ // next transition involving freeform windows.
+ // TODO(b/346564416): Modify logging for cancelled recents once it transition is
+ // changed. Also see how to account to time difference between actual enter time
+ // and
+ // time of this log. Also account for the missed session when exit happens just
+ // after
+ // a cancelled recents.
+ wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW
+ transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ else -> {
+ ProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "Unknown enter reason for transition type: %s",
+ transitionInfo.type,
+ )
+ EnterReason.UNKNOWN_ENTER
+ }
}
- }
wasPreviousTransitionExitByScreenOff = false
return enterReason
}
/** Get [ExitReason] for this session exit */
private fun getExitReason(transitionInfo: TransitionInfo): ExitReason =
- when {
+ when {
transitionInfo.type == WindowManager.TRANSIT_SLEEP -> {
wasPreviousTransitionExitByScreenOff = true
ExitReason.SCREEN_OFF
@@ -387,7 +396,7 @@ class DesktopModeLoggerTransitionObserver(
ProtoLog.w(
WM_SHELL_DESKTOP_MODE,
"Unknown exit reason for transition type: %s",
- transitionInfo.type
+ transitionInfo.type,
)
ExitReason.UNKNOWN_EXIT
}
@@ -413,8 +422,7 @@ class DesktopModeLoggerTransitionObserver(
}
companion object {
- @VisibleForTesting
- const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks"
+ @VisibleForTesting const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks"
@VisibleForTesting
const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY =
"debug.tracing." + VISIBLE_TASKS_COUNTER_NAME
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index d6fccd116061..9b3caca7b2d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -43,7 +43,7 @@ object DesktopModeTransitionTypes {
TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON,
TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW,
TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT,
- TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
+ TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN,
)
}
@@ -73,7 +73,7 @@ object DesktopModeTransitionTypes {
TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG,
TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON,
TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT,
- TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
+ TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN,
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index 2c432bcb55ab..301ba9e76fc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -75,7 +75,7 @@ class DesktopModeUiEventLogger(
instanceId: InstanceId,
uid: Int,
packageName: String,
- event: DesktopUiEventEnum
+ event: DesktopUiEventEnum,
) {
if (packageName.isEmpty() || uid < 0) {
logD("Skip logging since package name is empty or bad uid")
@@ -84,11 +84,12 @@ class DesktopModeUiEventLogger(
uiEventLogger.logWithInstanceId(event, uid, packageName, instanceId)
}
- private fun getUid(packageName: String, userId: Int): Int = try {
- packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid
- } catch (e: PackageManager.NameNotFoundException) {
- INVALID_PACKAGE_UID
- }
+ private fun getUid(packageName: String, userId: Int): Int =
+ try {
+ packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ INVALID_PACKAGE_UID
+ }
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
@@ -103,8 +104,12 @@ class DesktopModeUiEventLogger(
DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722),
@UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
+ @UiEvent(doc = "Tap on the window header restore button in desktop windowing mode")
+ DESKTOP_WINDOW_RESTORE_BUTTON_TAP(2017),
@UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724),
+ @UiEvent(doc = "Double tap on window header to restore from maximize in desktop windowing")
+ DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE(2018),
@UiEvent(doc = "Tap on the window Handle to open the Handle Menu")
DESKTOP_WINDOW_APP_HANDLE_TAP(1998),
@UiEvent(doc = "Tap on the desktop mode option under app handle menu")
@@ -136,7 +141,11 @@ class DesktopModeUiEventLogger(
@UiEvent(doc = "Tap on the tile to left option in the maximize button menu")
DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012),
@UiEvent(doc = "Tap on the tile to right option in the maximize button menu")
- DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013);
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013),
+ @UiEvent(doc = "Moving the desktop window by dragging the header")
+ DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG(2021),
+ @UiEvent(doc = "Double tap on the window header to refocus a desktop window")
+ DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS(2022);
override fun getId(): Int = mId
}
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 c7cf31081c8b..14623cf9e703 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
@@ -28,6 +28,7 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Rect
import android.os.SystemProperties
import android.util.Size
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
@@ -36,21 +37,14 @@ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int =
SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
-/**
- * Calculates the initial bounds to enter desktop, centered on the display.
- */
+/** Calculates the initial bounds to enter desktop, centered on the display. */
fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
val heightOffset = (displayLayout.height() - desiredHeight) / 2
val widthOffset = (displayLayout.width() - desiredWidth) / 2
- return Rect(
- widthOffset,
- heightOffset,
- desiredWidth + widthOffset,
- desiredHeight + heightOffset
- )
+ return Rect(widthOffset, heightOffset, desiredWidth + widthOffset, desiredHeight + heightOffset)
}
/**
@@ -62,7 +56,7 @@ fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
fun calculateInitialBounds(
displayLayout: DisplayLayout,
taskInfo: RunningTaskInfo,
- scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+ scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE,
): Rect {
val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
val appAspectRatio = calculateAspectRatio(taskInfo)
@@ -87,8 +81,10 @@ fun calculateInitialBounds(
if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
- Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
- idealSize.height)
+ Size(
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
+ idealSize.height,
+ )
} else {
// For landscape resizeable activities, simply apply ideal size.
idealSize
@@ -108,7 +104,7 @@ fun calculateInitialBounds(
// apply custom app width.
Size(
customPortraitWidthForLandscapeApp,
- taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight,
)
} else {
// For portrait resizeable activities, simply apply ideal size.
@@ -122,7 +118,7 @@ fun calculateInitialBounds(
maximizeSizeGivenAspectRatio(
taskInfo,
Size(customPortraitWidthForLandscapeApp, idealSize.height),
- appAspectRatio
+ appAspectRatio,
)
} else {
// For portrait unresizeable activities, calculate maximum size (within the
@@ -140,13 +136,10 @@ fun calculateInitialBounds(
}
/**
- * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking
- * resizability into consideration.
+ * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking resizability
+ * into consideration.
*/
-fun calculateMaximizeBounds(
- displayLayout: DisplayLayout,
- taskInfo: RunningTaskInfo,
-): Rect {
+fun calculateMaximizeBounds(displayLayout: DisplayLayout, taskInfo: RunningTaskInfo): Rect {
val stableBounds = Rect()
displayLayout.getStableBounds(stableBounds)
if (taskInfo.isResizeable) {
@@ -155,10 +148,13 @@ fun calculateMaximizeBounds(
} else {
// if non-resizable then calculate max bounds according to aspect ratio
val activityAspectRatio = calculateAspectRatio(taskInfo)
- val newSize = maximizeSizeGivenAspectRatio(taskInfo,
- Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
- return centerInArea(
- newSize, stableBounds, stableBounds.left, stableBounds.top)
+ val newSize =
+ maximizeSizeGivenAspectRatio(
+ taskInfo,
+ Size(stableBounds.width(), stableBounds.height()),
+ activityAspectRatio,
+ )
+ return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top)
}
}
@@ -169,7 +165,7 @@ fun calculateMaximizeBounds(
fun maximizeSizeGivenAspectRatio(
taskInfo: RunningTaskInfo,
targetArea: Size,
- aspectRatio: Float
+ aspectRatio: Float,
): Size {
val targetHeight = targetArea.height
val targetWidth = targetArea.width
@@ -211,16 +207,36 @@ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
minOf(appBounds.height(), appBounds.width()).toFloat()
}
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(taskInfo: RunningTaskInfo, displayController: DisplayController): Boolean {
+ val displayLayout =
+ displayController.getDisplayLayout(taskInfo.displayId)
+ ?: error("Could not get display layout for display=${taskInfo.displayId}")
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ return isTaskMaximized(taskInfo, stableBounds)
+}
+
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(taskInfo: RunningTaskInfo, stableBounds: Rect): Boolean {
+ val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+ return if (taskInfo.isResizeable) {
+ isTaskBoundsEqual(currentTaskBounds, stableBounds)
+ } else {
+ isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+ }
+}
+
/** Returns true if task's width or height is maximized else returns false. */
fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
return taskBounds.width() == stableBounds.width() ||
- taskBounds.height() == stableBounds.height()
+ taskBounds.height() == stableBounds.height()
}
/** Returns true if task bound is equal to stable bounds else returns false. */
fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
return taskBounds.width() == stableBounds.width() &&
- taskBounds.height() == stableBounds.height()
+ taskBounds.height() == stableBounds.height()
}
/**
@@ -248,8 +264,8 @@ private val TaskInfo.canChangeAspectRatio: Boolean
get() = isResizeable && !appCompatTaskInfo.hasMinAspectRatioOverride()
/**
- * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
- * entire screen, as area can be offset by left and top start.
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the entire
+ * screen, as area can be offset by left and top start.
*/
fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
val heightOffset = (areaBounds.height() - desiredSize.height) / 2
@@ -286,6 +302,6 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean {
}
private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean {
- return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled
- || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+ return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled ||
+ taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
}
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 80d8ecc127fe..cd37113666bb 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
@@ -53,6 +53,7 @@ import android.view.animation.DecelerateInterpolator;
import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -245,9 +246,17 @@ public class DesktopModeVisualIndicator {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mContext.getResources();
final DisplayMetrics metrics = resources.getDisplayMetrics();
- final int screenWidth = metrics.widthPixels;
- final int screenHeight = metrics.heightPixels;
-
+ final int screenWidth;
+ final int screenHeight;
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ screenWidth = displayLayout.width();
+ screenHeight = displayLayout.height();
+ } else {
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+ }
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
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 7fcb7678f6af..bccb609c41e8 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-import android.content.Context
import android.graphics.Rect
import android.graphics.Region
import android.util.ArrayMap
@@ -30,11 +29,8 @@ import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-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.annotations.ShellMainThread
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
-import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -42,26 +38,23 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/** Tracks desktop data for Android Desktop Windowing. */
-class DesktopRepository (
- private val context: Context,
- shellInit: ShellInit,
+class DesktopRepository(
private val persistentRepository: DesktopPersistentRepository,
- private val repositoryInitializer: DesktopRepositoryInitializer,
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
-){
-
+ val userId: Int,
+) {
/**
* Task data tracked per desktop.
*
* @property activeTasks task ids of active tasks currently or previously visible in Desktop
- * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+ * mode session. Tasks become inactive when task closes or when desktop mode session ends.
* @property visibleTasks task ids for active freeform tasks that are currently visible. There
- * might be other active tasks in desktop mode that are not visible.
+ * might be other active tasks in desktop mode that are not visible.
* @property minimizedTasks task ids for active freeform tasks that are currently minimized.
* @property closingTasks task ids for tasks that are going to close, but are currently visible.
* @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
* @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
- * (top is at index 0).
+ * (top is at index 0).
*/
private data class DesktopTaskData(
val activeTasks: ArraySet<Int> = ArraySet(),
@@ -72,14 +65,16 @@ class DesktopRepository (
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
) {
- fun deepCopy(): DesktopTaskData = DesktopTaskData(
- activeTasks = ArraySet(activeTasks),
- visibleTasks = ArraySet(visibleTasks),
- minimizedTasks = ArraySet(minimizedTasks),
- closingTasks = ArraySet(closingTasks),
- freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
- fullImmersiveTaskId = fullImmersiveTaskId
- )
+ fun deepCopy(): DesktopTaskData =
+ DesktopTaskData(
+ activeTasks = ArraySet(activeTasks),
+ visibleTasks = ArraySet(visibleTasks),
+ minimizedTasks = ArraySet(minimizedTasks),
+ closingTasks = ArraySet(closingTasks),
+ freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
+ fullImmersiveTaskId = fullImmersiveTaskId,
+ )
+
fun clear() {
activeTasks.clear()
visibleTasks.clear()
@@ -111,21 +106,12 @@ class DesktopRepository (
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
- private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() {
- /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
- fun getOrCreate(displayId: Int): DesktopTaskData =
- this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
- }
-
- init {
- if (DesktopModeStatus.canEnterDesktopMode(context)) {
- shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+ private val desktopTaskDataByDisplayId =
+ object : SparseArray<DesktopTaskData>() {
+ /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
+ fun getOrCreate(displayId: Int): DesktopTaskData =
+ this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
}
- }
-
- private fun initRepoFromPersistentStorage() {
- repositoryInitializer.initialize(this)
- }
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
@@ -137,9 +123,7 @@ class DesktopRepository (
visibleTasksListeners[visibleTasksListener] = executor
desktopTaskDataByDisplayId.keyIterator().forEach {
val visibleTaskCount = getVisibleTaskCount(it)
- executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount)
- }
+ executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) }
}
}
@@ -201,8 +185,7 @@ class DesktopRepository (
/** Removes task from active task list of displays excluding the [excludedDisplayId]. */
fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
- if ((displayId != excludedDisplayId)
- && desktopTaskData.activeTasks.remove(taskId)) {
+ if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) {
logD("Removed active task=%d displayId=%d", taskId, displayId)
updateActiveTasksListeners(displayId)
}
@@ -229,16 +212,18 @@ class DesktopRepository (
}
fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+
fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+
fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+
fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
/** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- desktopTaskDataSequence().any { it.visibleTasks
- .subtract(it.closingTasks)
- .subtract(it.minimizedTasks)
- .singleOrNull() == taskId
+ desktopTaskDataSequence().any {
+ it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() ==
+ taskId
}
fun getActiveTasks(displayId: Int): ArraySet<Int> =
@@ -272,10 +257,12 @@ class DesktopRepository (
/**
* Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
*
- * If task was visible on a different display with a different [displayId], removes from
- * the set of visible tasks on that display and notifies listeners.
+ * If task was visible on a different display with a different [displayId], removes from the set
+ * of visible tasks on that display and notifies listeners.
*/
fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
+ logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
+
if (isVisible) {
// If task is visible, remove it from any other display besides [displayId].
removeVisibleTask(taskId, excludedDisplayId = displayId)
@@ -293,8 +280,12 @@ class DesktopRepository (
}
val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
- logD("Update task visibility taskId=%d visible=%b displayId=%d",
- taskId, isVisible, displayId)
+ logD(
+ "Update task visibility taskId=%d visible=%b displayId=%d",
+ taskId,
+ isVisible,
+ displayId,
+ )
logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
notifyVisibleTaskListeners(displayId, newCount)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -332,9 +323,8 @@ class DesktopRepository (
/** Gets number of visible tasks on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int =
- desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also {
- logD("getVisibleTaskCount=$it")
- }
+ desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
+ ?: 0.also { logD("getVisibleTaskCount=$it") }
/**
* Adds task (or moves if it already exists) to the top of the ordered list.
@@ -343,7 +333,9 @@ class DesktopRepository (
*/
private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ desktopTaskDataByDisplayId.forEach { _, value ->
+ value.freeformTasksInZOrder.remove(taskId)
+ }
desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
@@ -357,9 +349,8 @@ class DesktopRepository (
if (displayId == INVALID_DISPLAY) {
// When a task vanishes it doesn't have a displayId. Find the display of the task and
// mark it as minimized.
- getDisplayIdForTask(taskId)?.let {
- minimizeTask(it, taskId)
- } ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
+ getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) }
+ ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
} else {
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
@@ -373,8 +364,8 @@ class DesktopRepository (
/** Unminimizes the task for [taskId] and [displayId] */
fun unminimizeTask(displayId: Int, taskId: Int) {
logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
- desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?:
- logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+ desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId)
+ ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
}
private fun getDisplayIdForTask(taskId: Int): Int? {
@@ -407,16 +398,14 @@ class DesktopRepository (
desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
boundsBeforeFullImmersiveByTaskId.remove(taskId)
- logD("Remaining freeform tasks: %s",
- desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
+ logD(
+ "Remaining freeform tasks: %s",
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(),
+ )
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
// Mark task as not in immersive if it was immersive.
- setTaskInFullImmersiveState(
- displayId = displayId,
- taskId = taskId,
- immersive = false
- )
+ setTaskInFullImmersiveState(displayId = displayId, taskId = taskId, immersive = false)
removeActiveTask(taskId)
removeVisibleTask(taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -495,15 +484,16 @@ class DesktopRepository (
persistentRepository.addOrUpdateDesktop(
// Use display id as desktop id for now since only once desktop per display
// is supported.
+ userId = userId,
desktopId = displayId,
visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
- freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder
+ freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder,
)
} catch (exception: Exception) {
logE(
"An exception occurred while updating the persistent repository \n%s",
- exception.stackTrace
+ exception.stackTrace,
)
}
}
@@ -529,6 +519,7 @@ class DesktopRepository (
)
pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
+ pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken")
}
}
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 94ac2e665f61..947a8dddb239 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
@@ -22,72 +22,81 @@ import android.window.DesktopModeFlags
import com.android.wm.shell.freeform.TaskChangeListener
/** Manages tasks handling specific to Android Desktop Mode. */
-class DesktopTaskChangeListener(
- private val desktopRepository: DesktopRepository,
-) : TaskChangeListener {
+class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUserRepositories) :
+ TaskChangeListener {
- override fun onTaskOpening(taskInfo: RunningTaskInfo) {
- if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
- desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
- return
+ override fun onTaskOpening(taskInfo: RunningTaskInfo) {
+ val desktopRepository: DesktopRepository =
+ desktopUserRepositories.getProfile(taskInfo.userId)
+ if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ return
+ }
+ if (isFreeformTask(taskInfo)) {
+ desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ }
}
- if (isFreeformTask(taskInfo)) {
- desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
- }
- }
- override fun onTaskChanging(taskInfo: RunningTaskInfo) {
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ override fun onTaskChanging(taskInfo: RunningTaskInfo) {
+ val desktopRepository: DesktopRepository =
+ desktopUserRepositories.getProfile(taskInfo.userId)
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
- // 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.
- desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ // 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.
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ }
}
- }
- // This method should only be used for scenarios where the task info changes are not propagated to
- // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver].
- // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
- // of race conditions and possible duplications with [onTaskChanging].
- override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method.
- }
+ // This method should only be used for scenarios where the task info changes are not propagated
+ // to
+ // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver].
+ // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
+ // of race conditions and possible duplications with [onTaskChanging].
+ override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method.
+ }
- override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
- if (!isFreeformTask(taskInfo)) {
- desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
+ val desktopRepository: DesktopRepository =
+ desktopUserRepositories.getProfile(taskInfo.userId)
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ if (!isFreeformTask(taskInfo)) {
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ }
+ // TODO: b/367268953 - Connect this with DesktopRepository for handling
+ // task moving to front for tasks in windowing mode.
}
- // TODO: b/367268953 - Connect this with DesktopRepository for handling
- // task moving to front for tasks in windowing mode.
- }
- override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
- // TODO: b/367268953 - Connect this with DesktopRepository.
- }
+ override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
+ // TODO: b/367268953 - Connect this with DesktopRepository.
+ }
- override fun onTaskClosing(taskInfo: RunningTaskInfo) {
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
- // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
- desktopRepository.isClosingTask(taskInfo.taskId)) {
- // A task that's vanishing should be removed:
- // - If it's closed by the X button which means it's marked as a closing task.
- desktopRepository.removeClosingTask(taskInfo.taskId)
- desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
- } else {
- desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false)
- desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ override fun onTaskClosing(taskInfo: RunningTaskInfo) {
+ val desktopRepository: DesktopRepository =
+ desktopUserRepositories.getProfile(taskInfo.userId)
+ if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+ // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
+ desktopRepository.isClosingTask(taskInfo.taskId)
+ ) {
+ // A task that's vanishing should be removed:
+ // - If it's closed by the X button which means it's marked as a closing task.
+ desktopRepository.removeClosingTask(taskInfo.taskId)
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+ } else {
+ desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false)
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ }
}
- }
- private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
index 65f12cf4a196..848d80ff4f0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
@@ -22,16 +22,14 @@ import android.graphics.Point
import android.graphics.Rect
import android.view.Gravity
import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft
import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight
import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center
import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft
import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight
-import com.android.wm.shell.R
-/**
- * The position of a task window in desktop mode.
- */
+/** The position of a task window in desktop mode. */
sealed class DesktopTaskPosition {
data object Center : DesktopTaskPosition() {
private const val WINDOW_HEIGHT_PROPORTION = 0.375
@@ -89,8 +87,8 @@ sealed class DesktopTaskPosition {
}
/**
- * Returns the top left coordinates for the window to be placed in the given
- * DesktopTaskPosition in the frame.
+ * Returns the top left coordinates for the window to be placed in the given DesktopTaskPosition
+ * in the frame.
*/
abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point
@@ -98,8 +96,8 @@ sealed class DesktopTaskPosition {
}
/**
- * If the app has specified horizontal or vertical gravity layout, don't change the
- * task position for cascading effect.
+ * If the app has specified horizontal or vertical gravity layout, don't change the task position
+ * for cascading effect.
*/
fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean {
taskInfo.topActivityInfo?.windowLayout?.let {
@@ -110,9 +108,7 @@ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean {
return true
}
-/**
- * Returns the current DesktopTaskPosition for a given window in the frame.
- */
+/** Returns the current DesktopTaskPosition for a given window in the frame. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
return when {
@@ -140,8 +136,8 @@ internal fun cascadeWindow(res: Resources, frame: Rect, prev: Rect, dest: Rect)
internal fun prevBoundsMovedAboveThreshold(res: Resources, prev: Rect, newBounds: Rect): Boolean {
// This is the required minimum dp for a task to be touchable.
- val moveThresholdPx = res.getDimensionPixelSize(
- R.dimen.freeform_required_visible_empty_space_in_header)
+ val moveThresholdPx =
+ res.getDimensionPixelSize(R.dimen.freeform_required_visible_empty_space_in_header)
val leftFar = newBounds.left - prev.left > moveThresholdPx
val topFar = newBounds.top - prev.top > moveThresholdPx
val rightFar = prev.right - newBounds.right > moveThresholdPx
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 c479ab382acb..0bc7ca982ec2 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
@@ -50,6 +50,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.widget.Toast
import android.window.DesktopModeFlags
@@ -67,7 +68,6 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE
import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.Flags.enableFlexibleSplit
@@ -84,10 +84,17 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
+import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -95,7 +102,8 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.ShellSharedConstants
+import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -127,15 +135,8 @@ import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
import java.util.function.Consumer
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
-import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
-import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
-import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
-import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
-import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
-import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
@@ -158,7 +159,7 @@ class DesktopTasksController(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
- private val taskRepository: DesktopRepository,
+ private val userRepositories: DesktopUserRepositories,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
@@ -176,6 +177,7 @@ class DesktopTasksController(
UserChangeListener {
private val desktopMode: DesktopModeImpl
+ private var taskRepository: DesktopRepository
private var visualIndicator: DesktopModeVisualIndicator? = null
private var userId: Int
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
@@ -203,8 +205,7 @@ class DesktopTasksController(
}
}
- @VisibleForTesting
- var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
+ @VisibleForTesting var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
@VisibleForTesting
var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener? = null
@@ -213,14 +214,14 @@ class DesktopTasksController(
val draggingTaskId
get() = dragToDesktopTransitionHandler.draggingTaskId
- @RecentsTransitionState
- private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING
+ @RecentsTransitionState private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING
private lateinit var splitScreenController: SplitScreenController
lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
// Launch cookie used to identify a drag and drop transition to fullscreen after it has begun.
// Used to prevent handleRequest from moving the new fullscreen task to freeform.
private var dragAndDropFullscreenCookie: Binder? = null
+ private var pendingPipTransitionAndTask: Pair<IBinder, Int>? = null
init {
desktopMode = DesktopModeImpl()
@@ -228,6 +229,7 @@ class DesktopTasksController(
shellInit.addInitCallback({ onInit() }, this)
}
userId = ActivityManager.getCurrentUser()
+ taskRepository = userRepositories.getProfile(userId)
}
private fun onInit() {
@@ -235,9 +237,9 @@ class DesktopTasksController(
shellCommandHandler.addDumpCallback(this::dump, this)
shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
shellController.addExternalInterface(
- ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
+ IDesktopMode.DESCRIPTOR,
{ createExternalInterface() },
- this
+ this,
)
shellController.addUserChangeListener(this)
transitions.addHandler(this)
@@ -247,7 +249,7 @@ class DesktopTasksController(
override fun onTransitionStateChanged(@RecentsTransitionState state: Int) {
logV(
"Recents transition state changed: %s",
- RecentsTransitionStateListener.stateToString(state)
+ RecentsTransitionStateListener.stateToString(state),
)
recentsTransitionState = state
desktopTilingDecorViewModel.onOverviewAnimationStateChange(
@@ -297,17 +299,17 @@ class DesktopTasksController(
bringDesktopAppsToFront(displayId, wct)
val transitionType = transitionType(remoteTransition)
- val handler = remoteTransition?.let {
- OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
- }
+ val handler =
+ remoteTransition?.let {
+ OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
+ }
transitions.startTransition(transitionType, wct, handler).also { t ->
handler?.setTransition(t)
}
}
/** Gets number of visible tasks in [displayId]. */
- fun visibleTaskCount(displayId: Int): Int =
- taskRepository.getVisibleTaskCount(displayId)
+ fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId)
/** Returns true if any tasks are visible in Desktop Mode. */
fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
@@ -318,29 +320,37 @@ class DesktopTasksController(
when (allFocusedTasks.size) {
0 -> return
// Full screen case
- 1 -> moveRunningTaskToDesktop(
- allFocusedTasks.single(), transitionSource = transitionSource)
+ 1 ->
+ moveRunningTaskToDesktop(
+ allFocusedTasks.single(),
+ transitionSource = transitionSource,
+ )
// Split-screen case where there are two focused tasks, then we find the child
// task to move to desktop.
- 2 -> moveRunningTaskToDesktop(
- getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
- transitionSource = transitionSource)
- else -> logW(
- "DesktopTasksController: Cannot enter desktop, expected less " +
- "than 3 focused tasks but found %d", allFocusedTasks.size)
+ 2 ->
+ moveRunningTaskToDesktop(
+ getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
+ transitionSource = transitionSource,
+ )
+ else ->
+ logW(
+ "DesktopTasksController: Cannot enter desktop, expected less " +
+ "than 3 focused tasks but found %d",
+ allFocusedTasks.size,
+ )
}
}
/**
- * Returns all focused tasks in full screen or split screen mode in [displayId] when
- * it is not the home activity.
+ * Returns all focused tasks in full screen or split screen mode in [displayId] when it is not
+ * the home activity.
*/
private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> =
shellTaskOrganizer.getRunningTasks(displayId).filter {
it.isFocused &&
- (it.windowingMode == WINDOWING_MODE_FULLSCREEN ||
- it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
- it.activityType != ACTIVITY_TYPE_HOME
+ (it.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ it.activityType != ACTIVITY_TYPE_HOME
}
/** Returns child task from two focused tasks in split screen mode. */
@@ -353,8 +363,15 @@ class DesktopTasksController(
}
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
- requireNotNull(tdaInfo) {
- "This method can only be called with the ID of a display having non-null DisplayArea."
+ // A non-organized display (e.g., non-trusted virtual displays used in CTS) doesn't have
+ // TDA.
+ if (tdaInfo == null) {
+ logW(
+ "forceEnterDesktop cannot find DisplayAreaInfo for displayId=%d. This could happen" +
+ " when the display is a non-trusted virtual display.",
+ displayId,
+ )
+ return false
}
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM
@@ -376,9 +393,9 @@ class DesktopTasksController(
}
private fun moveBackgroundTaskToDesktop(
- taskId: Int,
- wct: WindowContainerTransaction,
- transitionSource: DesktopModeTransitionSource,
+ taskId: Int,
+ wct: WindowContainerTransaction,
+ transitionSource: DesktopModeTransitionSource,
): Boolean {
if (recentTasksController?.findTaskInBackground(taskId) == null) {
logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
@@ -386,54 +403,62 @@ class DesktopTasksController(
}
logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
// TODO(342378842): Instead of using default display, support multiple displays
- val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
- DEFAULT_DISPLAY, wct, taskId)
- val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = DEFAULT_DISPLAY,
- excludeTaskId = taskId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
- )
+ val taskIdToMinimize =
+ bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = DEFAULT_DISPLAY,
+ excludeTaskId = taskId,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+ )
wct.startTask(
taskId,
- ActivityOptions.makeBasic().apply {
- launchWindowingMode = WINDOWING_MODE_FREEFORM
- }.toBundle(),
+ ActivityOptions.makeBasic()
+ .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+ .toBundle(),
)
// TODO(343149901): Add DPI changes for task launch
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
- desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
- /** Moves a running task to desktop. */
+ /** Moves a running task to desktop. */
fun moveRunningTaskToDesktop(
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
) {
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
- && isTopActivityExemptFromDesktopWindowing(context, task)) {
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
+ isTopActivityExemptFromDesktopWindowing(context, task)
+ ) {
logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
return
}
logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
exitSplitIfApplicable(wct, task)
- val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = task.displayId,
- excludeTaskId = task.taskId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
- )
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = task.displayId,
+ excludeTaskId = task.taskId,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+ )
// Bring other apps to front first
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
addMoveToDesktopChanges(wct, task)
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
- desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
}
@@ -448,11 +473,18 @@ class DesktopTasksController(
taskSurface: SurfaceControl,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
- interactionJankMonitor.begin(taskSurface, context, handler,
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ val jankConfigBuilder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
+ context,
+ taskSurface,
+ handler,
+ )
+ .setTimeout(APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS)
+ interactionJankMonitor.begin(jankConfigBuilder)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
- taskInfo.taskId,
- dragToDesktopValueAnimator
+ taskInfo,
+ dragToDesktopValueAnimator,
)
}
@@ -464,7 +496,7 @@ class DesktopTasksController(
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d",
- taskInfo.taskId
+ taskInfo.taskId,
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
@@ -472,12 +504,13 @@ class DesktopTasksController(
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
- val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = taskInfo.displayId,
- excludeTaskId = null,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH
- )
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = taskInfo.displayId,
+ excludeTaskId = null,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+ )
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
@@ -520,29 +553,50 @@ class DesktopTasksController(
performDesktopExitCleanupIfNeeded(taskId, wct)
taskRepository.addClosingTask(displayId, taskId)
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(
- displayId,
- taskId
- )
+ doesAnyTaskRequireTaskbarRounding(displayId, taskId)
)
- return desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.CLOSED
- ).asExit()?.runOnTransitionStart
+ return desktopImmersiveController
+ .exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.CLOSED,
+ )
+ .asExit()
+ ?.runOnTransitionStart
}
fun minimizeTask(taskInfo: RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+
+ val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
+ // If task is going to PiP, start a PiP transition instead of a minimize transition
+ if (isMinimizingToPip) {
+ val requestInfo = TransitionRequestInfo(
+ TRANSIT_PIP, /* triggerTask= */ null, taskInfo, /* remoteTransition= */ null,
+ /* displayChange= */ null, /* flags= */ 0
+ )
+ val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
+ wct.merge(requestRes.second, true)
+ pendingPipTransitionAndTask =
+ freeformTaskTransitionStarter.startPipTransition(wct) to taskInfo.taskId
+ return
+ }
+
+ minimizeTaskInner(taskInfo)
+ }
+
+ private fun minimizeTaskInner(taskInfo: RunningTaskInfo) {
val taskId = taskInfo.taskId
val displayId = taskInfo.displayId
val wct = WindowContainerTransaction()
performDesktopExitCleanupIfNeeded(taskId, wct)
// Notify immersive handler as it might need to exit immersive state.
- val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.MINIMIZED
- )
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+ )
wct.reorder(taskInfo.token, false)
val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
@@ -550,7 +604,7 @@ class DesktopTasksController(
it.addPendingMinimizeChange(
transition = transition,
displayId = displayId,
- taskId = taskId
+ taskId = taskId,
)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
@@ -577,7 +631,7 @@ class DesktopTasksController(
splitScreenController.prepareExitSplitScreen(
wct,
splitScreenController.getStageOfTask(taskInfo.taskId),
- EXIT_REASON_DESKTOP_MODE
+ EXIT_REASON_DESKTOP_MODE,
)
splitScreenController.transitionHandler?.onSplitToDesktop()
}
@@ -597,22 +651,24 @@ class DesktopTasksController(
private fun moveToFullscreenWithAnimation(
task: RunningTaskInfo,
position: Point,
- transitionSource: DesktopModeTransitionSource
+ transitionSource: DesktopModeTransitionSource,
) {
logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task)
exitDesktopTaskTransitionHandler.startTransition(
- transitionSource,
- wct,
- position,
- mOnAnimationFinishedCallback
- )
+ transitionSource,
+ wct,
+ position,
+ mOnAnimationFinishedCallback,
+ )
// handles case where we are moving to full screen without closing all DW tasks.
if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) {
- desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION
+ )
}
}
@@ -642,16 +698,11 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
wct.startTask(
taskId,
- ActivityOptions.makeBasic().apply {
- launchWindowingMode = WINDOWING_MODE_FREEFORM
- }.toBundle(),
- )
- startLaunchTransition(
- TRANSIT_OPEN,
- wct,
- taskId,
- remoteTransition = remoteTransition
+ ActivityOptions.makeBasic()
+ .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+ .toBundle(),
)
+ startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition = remoteTransition)
}
/**
@@ -686,29 +737,32 @@ class DesktopTasksController(
remoteTransition: RemoteTransition? = null,
displayId: Int = DEFAULT_DISPLAY,
): IBinder {
- val taskIdToMinimize = if (launchingTaskId != null) {
- addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
- } else {
- logW("Starting desktop task launch without checking the task-limit")
- // TODO(b/378920066): This currently does not respect the desktop window limit.
- // It's possible that |launchingTaskId| is null when launching using an intent, and
- // the task-limit should be respected then too.
- null
- }
- val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- displayId = displayId,
- excludeTaskId = launchingTaskId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
- )
- if (remoteTransition == null) {
- val t = desktopMixedTransitionHandler.startLaunchTransition(
- transitionType = transitionType,
+ val taskIdToMinimize =
+ if (launchingTaskId != null) {
+ addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
+ } else {
+ logW("Starting desktop task launch without checking the task-limit")
+ // TODO(b/378920066): This currently does not respect the desktop window limit.
+ // It's possible that |launchingTaskId| is null when launching using an intent, and
+ // the task-limit should be respected then too.
+ null
+ }
+ val exitImmersiveResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
- taskId = launchingTaskId,
- minimizingTaskId = taskIdToMinimize,
- exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
+ displayId = displayId,
+ excludeTaskId = launchingTaskId,
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
+ if (remoteTransition == null) {
+ val t =
+ desktopMixedTransitionHandler.startLaunchTransition(
+ transitionType = transitionType,
+ wct = wct,
+ taskId = launchingTaskId,
+ minimizingTaskId = taskIdToMinimize,
+ exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
+ )
taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
@@ -722,7 +776,11 @@ class DesktopTasksController(
}
val remoteTransitionHandler =
DesktopWindowLimitRemoteHandler(
- mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
+ mainExecutor,
+ rootTaskDisplayAreaOrganizer,
+ remoteTransition,
+ taskIdToMinimize,
+ )
val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
remoteTransitionHandler.setTransition(t)
taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
@@ -793,34 +851,23 @@ class DesktopTasksController(
* bounds) and a free floating state (either the last saved bounds if available or the default
* bounds otherwise).
*/
- fun toggleDesktopTaskSize(
- taskInfo: RunningTaskInfo,
- resizeTrigger: ResizeTrigger,
- inputMethod: InputMethod,
- maximizeCujRecorder: (() -> Unit)? = null,
- unmaximizeCujRecorder: (() -> Unit)? = null,
- ) {
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, interaction: ToggleTaskSizeInteraction) {
val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
desktopModeEventLogger.logTaskResizingStarted(
- resizeTrigger,
- inputMethod,
+ interaction.resizeTrigger,
+ interaction.inputMethod,
taskInfo,
currentTaskBounds.width(),
currentTaskBounds.height(),
- displayController
+ displayController,
)
-
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
- val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
val destinationBounds = Rect()
-
- val isMaximized = isTaskMaximized(taskInfo, stableBounds)
+ val isMaximized = interaction.direction == ToggleTaskSizeInteraction.Direction.RESTORE
// If the task is currently maximized, we will toggle it not to be and vice versa. This is
// helpful to eliminate the current task from logic to calculate taskbar corner rounding.
- val willMaximize = !isMaximized
+ val willMaximize = interaction.direction == ToggleTaskSizeInteraction.Direction.MAXIMIZE
if (isMaximized) {
- unmaximizeCujRecorder?.invoke()
// The desktop task is at the maximized width and/or height of the stable bounds.
// If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
// Otherwise, toggle to the default bounds.
@@ -836,7 +883,6 @@ class DesktopTasksController(
}
}
} else {
- maximizeCujRecorder?.invoke()
// Save current bounds so that task can be restored back to original bounds if necessary
// and toggle to the stable bounds.
desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
@@ -845,36 +891,41 @@ class DesktopTasksController(
destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo))
}
-
val shouldRestoreToSnap =
isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds)
logD("willMaximize = %s", willMaximize)
logD("shouldRestoreToSnap = %s", shouldRestoreToSnap)
- val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap ||
+ val doesAnyTaskRequireTaskbarRounding =
+ willMaximize ||
+ shouldRestoreToSnap ||
doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId)
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+ interaction.uiEvent?.let { uiEvent -> desktopModeUiEventLogger.log(taskInfo, uiEvent) }
desktopModeEventLogger.logTaskResizingEnded(
- resizeTrigger, inputMethod,
- taskInfo, destinationBounds.width(),
- destinationBounds.height(), displayController
+ interaction.resizeTrigger,
+ interaction.inputMethod,
+ taskInfo,
+ destinationBounds.width(),
+ destinationBounds.height(),
+ displayController,
+ )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(
+ wct,
+ interaction.animationStartBounds,
)
- toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
}
private fun dragToMaximizeDesktopTask(
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
currentDragBounds: Rect,
- motionEvent: MotionEvent
+ motionEvent: MotionEvent,
) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
- if (isTaskMaximized(taskInfo, stableBounds)) {
+ if (isTaskMaximized(taskInfo, displayController)) {
// Handle the case where we attempt to drag-to-maximize when already maximized: the task
// position won't need to change but we want to animate the surface going back to the
// maximized position.
@@ -892,8 +943,12 @@ class DesktopTasksController(
toggleDesktopTaskSize(
taskInfo,
- ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
- DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+ ToggleTaskSizeInteraction(
+ direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP,
+ inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+ animationStartBounds = currentDragBounds,
+ ),
)
}
@@ -904,29 +959,19 @@ class DesktopTasksController(
} else {
// if non-resizable then calculate max bounds according to aspect ratio
val activityAspectRatio = calculateAspectRatio(taskInfo)
- val newSize = maximizeSizeGivenAspectRatio(taskInfo,
- Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
- return centerInArea(
- newSize, stableBounds, stableBounds.left, stableBounds.top)
- }
- }
-
- private fun isTaskMaximized(
- taskInfo: RunningTaskInfo,
- stableBounds: Rect
- ): Boolean {
- val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
-
- return if (taskInfo.isResizeable) {
- isTaskBoundsEqual(currentTaskBounds, stableBounds)
- } else {
- isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+ val newSize =
+ maximizeSizeGivenAspectRatio(
+ taskInfo,
+ Size(stableBounds.width(), stableBounds.height()),
+ activityAspectRatio,
+ )
+ return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top)
}
}
private fun isMaximizedToStableBoundsEdges(
taskInfo: RunningTaskInfo,
- stableBounds: Rect
+ stableBounds: Rect,
): Boolean {
val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
return isTaskBoundsEqual(currentTaskBounds, stableBounds)
@@ -935,18 +980,16 @@ class DesktopTasksController(
/** Returns if current task bound is snapped to half screen */
private fun isTaskSnappedToHalfScreen(
taskInfo: RunningTaskInfo,
- taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds
+ taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds,
): Boolean =
getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds ||
- getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
+ getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
@VisibleForTesting
- fun doesAnyTaskRequireTaskbarRounding(
- displayId: Int,
- excludeTaskId: Int? = null,
- ): Boolean {
+ fun doesAnyTaskRequireTaskbarRounding(displayId: Int, excludeTaskId: Int? = null): Boolean {
val doesAnyTaskRequireTaskbarRounding =
- taskRepository.getExpandedTasksOrdered(displayId)
+ taskRepository
+ .getExpandedTasksOrdered(displayId)
// exclude current task since maximize/restore transition has not taken place yet.
.filterNot { taskId -> taskId == excludeTaskId }
.any { taskId ->
@@ -956,14 +999,14 @@ class DesktopTasksController(
logD("taskInfo = %s", taskInfo)
logD(
"isTaskSnappedToHalfScreen(taskInfo) = %s",
- isTaskSnappedToHalfScreen(taskInfo)
+ isTaskSnappedToHalfScreen(taskInfo),
)
logD(
"isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s",
- isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+ isMaximizedToStableBoundsEdges(taskInfo, stableBounds),
)
- isTaskSnappedToHalfScreen(taskInfo)
- || isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+ isTaskSnappedToHalfScreen(taskInfo) ||
+ isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
}
logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding)
@@ -976,7 +1019,7 @@ class DesktopTasksController(
* @param taskInfo current task that is being snap-resized via dragging or maximize menu button
* @param taskSurface the leash of the task being dragged
* @param currentDragBounds current position of the task leash being dragged (or current task
- * bounds if being snapped resize via maximize menu button)
+ * bounds if being snapped resize via maximize menu button)
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
fun snapToHalfScreen(
@@ -994,7 +1037,7 @@ class DesktopTasksController(
taskInfo,
currentDragBounds.width(),
currentDragBounds.height(),
- displayController
+ displayController,
)
val destinationBounds = getSnapBounds(taskInfo, position)
@@ -1008,12 +1051,13 @@ class DesktopTasksController(
)
if (DesktopModeFlags.ENABLE_TILE_RESIZING.isTrue()) {
- val isTiled = desktopTilingDecorViewModel.snapToHalfScreen(
- taskInfo,
- desktopWindowDecoration,
- position,
- currentDragBounds,
- )
+ val isTiled =
+ desktopTilingDecorViewModel.snapToHalfScreen(
+ taskInfo,
+ desktopWindowDecoration,
+ position,
+ currentDragBounds,
+ )
if (isTiled) {
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
}
@@ -1054,10 +1098,11 @@ class DesktopTasksController(
) {
if (!isSnapResizingAllowed(taskInfo)) {
Toast.makeText(
- getContext(),
- R.string.desktop_mode_non_resizable_snap_text,
- Toast.LENGTH_SHORT
- ).show()
+ getContext(),
+ R.string.desktop_mode_non_resizable_snap_text,
+ Toast.LENGTH_SHORT,
+ )
+ .show()
return
}
@@ -1068,11 +1113,10 @@ class DesktopTasksController(
position,
resizeTrigger,
inputMethod,
- desktopModeWindowDecoration
+ desktopModeWindowDecoration,
)
}
-
@VisibleForTesting
fun handleSnapResizingTaskOnDrag(
taskInfo: RunningTaskInfo,
@@ -1086,7 +1130,11 @@ class DesktopTasksController(
releaseVisualIndicator()
if (!isSnapResizingAllowed(taskInfo)) {
interactionJankMonitor.begin(
- taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+ taskSurface,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_SNAP_RESIZE,
+ "drag_non_resizable",
)
// reposition non-resizable app back to its original position before being dragged
@@ -1097,20 +1145,26 @@ class DesktopTasksController(
endBounds = dragStartBounds,
doOnEnd = {
Toast.makeText(
- context,
- com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
- Toast.LENGTH_SHORT
- ).show()
+ context,
+ com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
+ Toast.LENGTH_SHORT,
+ )
+ .show()
},
)
} else {
- val resizeTrigger = if (position == SnapPosition.LEFT) {
- ResizeTrigger.DRAG_LEFT
- } else {
- ResizeTrigger.DRAG_RIGHT
- }
+ val resizeTrigger =
+ if (position == SnapPosition.LEFT) {
+ ResizeTrigger.DRAG_LEFT
+ } else {
+ ResizeTrigger.DRAG_RIGHT
+ }
interactionJankMonitor.begin(
- taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+ taskSurface,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_SNAP_RESIZE,
+ "drag_resizable",
)
snapToHalfScreen(
taskInfo,
@@ -1140,7 +1194,7 @@ class DesktopTasksController(
stableBounds.left,
stableBounds.top,
stableBounds.left + destinationWidth,
- stableBounds.bottom
+ stableBounds.bottom,
)
}
SnapPosition.RIGHT -> {
@@ -1148,7 +1202,7 @@ class DesktopTasksController(
stableBounds.right - destinationWidth,
stableBounds.top,
stableBounds.right,
- stableBounds.bottom
+ stableBounds.bottom,
)
}
}
@@ -1168,36 +1222,35 @@ class DesktopTasksController(
private fun bringDesktopAppsToFrontBeforeShowingNewTask(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskIdInFront: Int
+ newTaskIdInFront: Int,
): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
private fun bringDesktopAppsToFront(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskIdInFront: Int? = null
+ newTaskIdInFront: Int? = null,
): Int? {
logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
// Move home to front, ensures that we go back home when all desktop windows are closed
moveHomeTask(wct, toTop = true)
// Currently, we only handle the desktop on the default display really.
- if (displayId == DEFAULT_DISPLAY
- && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
+ if (
+ (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) &&
+ ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ ) {
// Add translucent wallpaper activity to show the wallpaper underneath
- addWallpaperActivity(wct)
+ addWallpaperActivity(displayId, wct)
}
- val expandedTasksOrderedFrontToBack =
- taskRepository.getExpandedTasksOrdered(displayId)
+ val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId)
// If we're adding a new Task we might need to minimize an old one
// TODO(b/365725441): Handle non running task minimization
val taskIdToMinimize: Int? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
- desktopTasksLimiter.get()
- .getTaskIdToMinimize(
- expandedTasksOrderedFrontToBack,
- newTaskIdInFront
- )
+ desktopTasksLimiter
+ .get()
+ .getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
} else {
null
}
@@ -1215,15 +1268,16 @@ class DesktopTasksController(
// Task is not running, start it
wct.startTask(
taskId,
- ActivityOptions.makeBasic().apply {
- launchWindowingMode = WINDOWING_MODE_FREEFORM
- }.toBundle(),
+ ActivityOptions.makeBasic()
+ .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+ .toBundle(),
)
}
}
- taskbarDesktopTaskListener?.
- onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(displayId)
+ )
return taskIdToMinimize
}
@@ -1235,11 +1289,10 @@ class DesktopTasksController(
?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
}
- private fun addWallpaperActivity(wct: WindowContainerTransaction) {
+ private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
val userHandle = UserHandle.of(userId)
- val userContext =
- context.createContextAsUser(userHandle, /* flags= */ 0)
+ val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
val options =
@@ -1247,6 +1300,9 @@ class DesktopTasksController(
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ launchDisplayId = displayId
+ }
}
val pendingIntent =
PendingIntent.getActivityAsUser(
@@ -1254,8 +1310,8 @@ class DesktopTasksController(
/* requestCode= */ 0,
intent,
PendingIntent.FLAG_IMMUTABLE,
- /* bundle= */ null,
- userHandle
+ /* options= */ null,
+ userHandle,
)
wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
}
@@ -1275,13 +1331,14 @@ class DesktopTasksController(
if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
return
}
- desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION
+ )
if (taskRepository.wallpaperActivityToken != null) {
removeWallpaperActivity(wct)
}
}
-
fun releaseVisualIndicator() {
val t = SurfaceControl.Transaction()
visualIndicator?.releaseVisualIndicator(t)
@@ -1305,15 +1362,30 @@ class DesktopTasksController(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
// This handler should never be the sole handler, so should not animate anything.
return false
}
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishT: Transaction?
+ ) {
+ pendingPipTransitionAndTask?.let { (pipTransition, taskId) ->
+ if (transition == pipTransition) {
+ if (aborted) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { minimizeTaskInner(it) }
+ }
+ pendingPipTransitionAndTask = null
+ }
+ }
+ }
+
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? {
logV("handleRequest request=%s", request)
// Check if we should skip handling this transition
@@ -1376,11 +1448,8 @@ class DesktopTasksController(
// Check if freeform task launch during recents should be handled
shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
- TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
- task,
- transition,
- request.type
- )
+ 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)
// Check if fullscreen task should be updated
@@ -1397,19 +1466,16 @@ class DesktopTasksController(
}
/** Whether the given [change] in the [transition] is a known desktop change. */
- fun isDesktopChange(
- transition: IBinder,
- change: TransitionInfo.Change,
- ): Boolean {
+ fun isDesktopChange(transition: IBinder, change: TransitionInfo.Change): Boolean {
// Only the immersive controller is currently involved in mixed transitions.
- return Flags.enableFullyImmersiveInDesktop()
- && desktopImmersiveController.isImmersiveChange(transition, change)
+ return Flags.enableFullyImmersiveInDesktop() &&
+ desktopImmersiveController.isImmersiveChange(transition, change)
}
/**
- * Whether the given transition [info] will potentially include a desktop change, in which
- * case the transition should be treated as mixed so that the change is in part animated by
- * one of the desktop transition handlers.
+ * Whether the given transition [info] will potentially include a desktop change, in which case
+ * the transition should be treated as mixed so that the change is in part animated by one of
+ * the desktop transition handlers.
*/
fun shouldPlayDesktopAnimation(info: TransitionRequestInfo): Boolean {
// Only immersive mixed transition are currently supported.
@@ -1453,7 +1519,7 @@ class DesktopTasksController(
change,
startTransaction,
finishTransaction,
- finishCallback
+ finishCallback,
)
}
@@ -1470,7 +1536,10 @@ class DesktopTasksController(
if (!DesktopModeStatus.useRoundedCorners()) {
return
}
- val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ val cornerRadius =
+ context.resources
+ .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ .toFloat()
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
@@ -1478,13 +1547,14 @@ class DesktopTasksController(
/** Returns whether an existing desktop task is being relaunched in freeform or not. */
private fun isFreeformRelaunch(triggerTask: RunningTaskInfo?, request: TransitionRequestInfo) =
- (triggerTask != null && triggerTask.windowingMode == WINDOWING_MODE_FREEFORM
- && TransitionUtil.isOpeningType(request.type)
- && taskRepository.isActiveTask(triggerTask.taskId))
+ (triggerTask != null &&
+ triggerTask.windowingMode == WINDOWING_MODE_FREEFORM &&
+ TransitionUtil.isOpeningType(request.type) &&
+ taskRepository.isActiveTask(triggerTask.taskId))
private fun isIncompatibleTask(task: TaskInfo) =
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
- && isTopActivityExemptFromDesktopWindowing(context, task)
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
+ isTopActivityExemptFromDesktopWindowing(context, task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
return ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() &&
@@ -1493,67 +1563,67 @@ class DesktopTasksController(
}
/** Open an existing instance of an app. */
- fun openInstance(
- callingTask: RunningTaskInfo,
- requestedTaskId: Int
- ) {
- val wct = WindowContainerTransaction()
- val options = createNewWindowOptions(callingTask)
- if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
- wct.startTask(requestedTaskId, options.toBundle())
- val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
- callingTask.displayId, wct, requestedTaskId)
- val exitResult = desktopImmersiveController
- .exitImmersiveIfApplicable(
- wct = wct,
- displayId = callingTask.displayId,
- excludeTaskId = requestedTaskId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+ fun openInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
+ if (callingTask.isFreeform) {
+ val requestedTaskInfo = shellTaskOrganizer.getRunningTaskInfo(requestedTaskId)
+ if (requestedTaskInfo?.isFreeform == true) {
+ // If requested task is an already open freeform task, just move it to front.
+ moveTaskToFront(requestedTaskId)
+ } else {
+ moveBackgroundTaskToDesktop(
+ requestedTaskId,
+ WindowContainerTransaction(),
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
)
- val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
- taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
- addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ }
} else {
+ val options = createNewWindowOptions(callingTask)
val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
- splitScreenController.startTask(requestedTaskId, splitPosition,
- options.toBundle(), null /* hideTaskToken */)
+ splitScreenController.startTask(
+ requestedTaskId,
+ splitPosition,
+ options.toBundle(),
+ null, /* hideTaskToken */
+ )
}
}
/** Create an Intent to open a new window of a task. */
- fun openNewWindow(
- callingTaskInfo: RunningTaskInfo
- ) {
+ fun openNewWindow(callingTaskInfo: RunningTaskInfo) {
// TODO(b/337915660): Add a transition handler for these; animations
// need updates in some cases.
val baseActivity = callingTaskInfo.baseActivity ?: return
- val fillIn: Intent = context.packageManager
- .getLaunchIntentForPackage(
- baseActivity.packageName
- ) ?: return
- fillIn
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
- val launchIntent = PendingIntent.getActivity(
- context,
- /* requestCode= */ 0,
- fillIn,
- PendingIntent.FLAG_IMMUTABLE
- )
+ val fillIn: Intent =
+ context.packageManager.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+ fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ val launchIntent =
+ PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ fillIn,
+ PendingIntent.FLAG_IMMUTABLE,
+ )
val options = createNewWindowOptions(callingTaskInfo)
when (options.launchWindowingMode) {
WINDOWING_MODE_MULTI_WINDOW -> {
- val splitPosition = splitScreenController
- .determineNewInstancePosition(callingTaskInfo)
+ val splitPosition =
+ splitScreenController.determineNewInstancePosition(callingTaskInfo)
// TODO(b/349828130) currently pass in index_undefined until we can revisit these
// specific cases in the future.
- val splitIndex = if (enableFlexibleSplit())
- splitScreenController.determineNewInstanceIndex(callingTaskInfo) else
- SPLIT_INDEX_UNDEFINED
+ val splitIndex =
+ if (enableFlexibleSplit())
+ splitScreenController.determineNewInstanceIndex(callingTaskInfo)
+ else SPLIT_INDEX_UNDEFINED
splitScreenController.startIntent(
- launchIntent, context.userId, fillIn, splitPosition,
- options.toBundle(), null /* hideTaskToken */,
- true /* forceLaunchNewTask */, splitIndex)
+ launchIntent,
+ context.userId,
+ fillIn,
+ splitPosition,
+ options.toBundle(),
+ null /* hideTaskToken */,
+ true /* forceLaunchNewTask */,
+ splitIndex,
+ )
}
WINDOWING_MODE_FREEFORM -> {
val wct = WindowContainerTransaction()
@@ -1562,36 +1632,39 @@ class DesktopTasksController(
transitionType = TRANSIT_OPEN,
wct = wct,
launchingTaskId = null,
- displayId = callingTaskInfo.displayId
+ displayId = callingTaskInfo.displayId,
)
}
}
}
private fun createNewWindowOptions(callingTask: RunningTaskInfo): ActivityOptions {
- val newTaskWindowingMode = when {
- callingTask.isFreeform -> {
- WINDOWING_MODE_FREEFORM
- }
- callingTask.isFullscreen || callingTask.isMultiWindow -> {
- WINDOWING_MODE_MULTI_WINDOW
- }
- else -> {
- error("Invalid windowing mode: ${callingTask.windowingMode}")
- }
- }
- val bounds = when (newTaskWindowingMode) {
- WINDOWING_MODE_FREEFORM -> {
- displayController.getDisplayLayout(callingTask.displayId)
- ?.let { getInitialBounds(it, callingTask, callingTask.displayId) }
- }
- WINDOWING_MODE_MULTI_WINDOW -> {
- Rect()
+ val newTaskWindowingMode =
+ when {
+ callingTask.isFreeform -> {
+ WINDOWING_MODE_FREEFORM
+ }
+ callingTask.isFullscreen || callingTask.isMultiWindow -> {
+ WINDOWING_MODE_MULTI_WINDOW
+ }
+ else -> {
+ error("Invalid windowing mode: ${callingTask.windowingMode}")
+ }
}
- else -> {
- error("Invalid windowing mode: $newTaskWindowingMode")
+ val bounds =
+ when (newTaskWindowingMode) {
+ WINDOWING_MODE_FREEFORM -> {
+ displayController.getDisplayLayout(callingTask.displayId)?.let {
+ getInitialBounds(it, callingTask, callingTask.displayId)
+ }
+ }
+ WINDOWING_MODE_MULTI_WINDOW -> {
+ Rect()
+ }
+ else -> {
+ error("Invalid windowing mode: $newTaskWindowingMode")
+ }
}
- }
return ActivityOptions.makeBasic().apply {
launchWindowingMode = newTaskWindowingMode
pendingIntentBackgroundActivityStartMode =
@@ -1617,7 +1690,7 @@ class DesktopTasksController(
private fun handleFreeformTaskLaunch(
task: RunningTaskInfo,
- transition: IBinder
+ transition: IBinder,
): WindowContainerTransaction? {
logV("handleFreeformTaskLaunch")
if (keyguardManager.isKeyguardLocked) {
@@ -1644,8 +1717,10 @@ class DesktopTasksController(
// 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.
- if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()
- && !taskRepository.isVisibleTask(task.taskId)) {
+ if (
+ DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
+ !taskRepository.isVisibleTask(task.taskId)
+ ) {
val displayLayout = displayController.getDisplayLayout(task.displayId)
if (displayLayout != null) {
val initialBounds = Rect(task.configuration.windowConfiguration.bounds)
@@ -1680,7 +1755,7 @@ class DesktopTasksController(
private fun handleFullscreenTaskLaunch(
task: RunningTaskInfo,
- transition: IBinder
+ transition: IBinder,
): WindowContainerTransaction? {
logV("handleFullscreenTaskLaunch")
if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) {
@@ -1691,8 +1766,10 @@ class DesktopTasksController(
// 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)) {
+ 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)
}
@@ -1706,7 +1783,7 @@ class DesktopTasksController(
transition,
wct,
task.displayId,
- reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH
+ reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
}
} else if (taskRepository.isActiveTask(task.taskId)) {
@@ -1737,10 +1814,13 @@ class DesktopTasksController(
}
/** Handle task closing by removing wallpaper activity if it's the last active task */
- private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? {
+ private fun handleTaskClosing(
+ task: RunningTaskInfo,
+ transition: IBinder,
+ requestType: Int,
+ ): WindowContainerTransaction? {
logV("handleTaskClosing")
- if (!isDesktopModeShowing(task.displayId))
- return null
+ if (!isDesktopModeShowing(task.displayId)) return null
val wct = WindowContainerTransaction()
performDesktopExitCleanupIfNeeded(task.taskId, wct)
@@ -1748,28 +1828,18 @@ class DesktopTasksController(
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
taskRepository.addClosingTask(task.displayId, task.taskId)
desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
- } else if (requestType == TRANSIT_CLOSE) {
- // Handle closing tasks, tasks that are going to back are handled in
- // [DesktopTasksTransitionObserver].
- desktopMixedTransitionHandler.addPendingMixedTransition(
- DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
- transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1
- )
- )
}
+
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(
- task.displayId,
- task.taskId
- )
+ doesAnyTaskRequireTaskbarRounding(task.displayId, task.taskId)
)
return if (wct.isEmpty) null else wct
}
/**
* 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.
+ * 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.
*/
@VisibleForTesting
fun addMoveToDesktopChanges(
@@ -1804,11 +1874,12 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
displayId: Int,
): Rect {
- val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
- calculateInitialBounds(displayLayout, taskInfo)
- } else {
- calculateDefaultDesktopTaskBounds(displayLayout)
- }
+ val bounds =
+ if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
+ calculateInitialBounds(displayLayout, taskInfo)
+ } else {
+ calculateDefaultDesktopTaskBounds(displayLayout)
+ }
if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
cascadeWindow(bounds, displayLayout, displayId)
@@ -1818,7 +1889,7 @@ class DesktopTasksController(
private fun addMoveToFullscreenChanges(
wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo
+ taskInfo: RunningTaskInfo,
) {
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
@@ -1845,8 +1916,12 @@ class DesktopTasksController(
val activeTasks = taskRepository.getExpandedTasksOrdered(displayId)
activeTasks.firstOrNull()?.let { activeTask ->
shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
- cascadeWindow(context.resources, stableBounds,
- it.configuration.windowConfiguration.bounds, bounds)
+ cascadeWindow(
+ context.resources,
+ stableBounds,
+ it.configuration.windowConfiguration.bounds,
+ bounds,
+ )
}
}
}
@@ -1872,24 +1947,19 @@ class DesktopTasksController(
private fun addAndGetMinimizeChanges(
displayId: Int,
wct: WindowContainerTransaction,
- newTaskId: Int
+ newTaskId: Int,
): Int? {
if (!desktopTasksLimiter.isPresent) return null
- return desktopTasksLimiter
- .get()
- .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId)
+ return desktopTasksLimiter.get().addAndGetMinimizeTaskChanges(displayId, wct, newTaskId)
}
- private fun addPendingMinimizeTransition(
- transition: IBinder,
- taskIdToMinimize: Int,
- ) {
+ private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
it.addPendingMinimizeChange(
transition = transition,
displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
- taskId = taskIdToMinimize
+ taskId = taskIdToMinimize,
)
}
}
@@ -1899,13 +1969,21 @@ class DesktopTasksController(
launchTaskId: Int,
minimizeTaskId: Int?,
) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
return
}
// TODO b/359523924: pass immersive task here?
desktopMixedTransitionHandler.addPendingMixedTransition(
DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
- transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
+ transition,
+ launchTaskId,
+ minimizeTaskId,
+ /* exitingImmersiveTask= */ null,
+ )
+ )
}
fun removeDesktop(displayId: Int) {
@@ -1940,10 +2018,7 @@ class DesktopTasksController(
* changes if this transition is enabled.
*/
@JvmOverloads
- fun requestSplit(
- taskInfo: RunningTaskInfo,
- leftOrTop: Boolean = false
- ) {
+ fun requestSplit(taskInfo: RunningTaskInfo, leftOrTop: Boolean = false) {
// If a drag to desktop is in progress, we want to enter split select
// even if the requesting task is already in split.
val isDragging = dragToDesktopTransitionHandler.inProgress
@@ -1951,11 +2026,12 @@ class DesktopTasksController(
if (shouldRequestSplit) {
if (isDragging) {
releaseVisualIndicator()
- val cancelState = if (leftOrTop) {
- DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
- } else {
- DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
- }
+ val cancelState =
+ if (leftOrTop) {
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
+ } else {
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
+ }
dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState)
} else {
val wct = WindowContainerTransaction()
@@ -1964,7 +2040,7 @@ class DesktopTasksController(
taskInfo,
wct,
if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
- taskInfo.configuration.windowConfiguration.bounds
+ taskInfo.configuration.windowConfiguration.bounds,
)
}
}
@@ -1999,12 +2075,17 @@ class DesktopTasksController(
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
inputX: Float,
- taskBounds: Rect
+ taskBounds: Rect,
) {
if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
- updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat(),
- DragStartState.FROM_FREEFORM)
+ updateVisualIndicator(
+ taskInfo,
+ taskSurface,
+ inputX,
+ taskBounds.top.toFloat(),
+ DragStartState.FROM_FREEFORM,
+ )
}
fun updateVisualIndicator(
@@ -2012,7 +2093,7 @@ class DesktopTasksController(
taskSurface: SurfaceControl?,
inputX: Float,
taskTop: Float,
- dragStartState: DragStartState
+ dragStartState: DragStartState,
): DesktopModeVisualIndicator.IndicatorType {
// If the visual indicator does not exist, create it.
val indicator =
@@ -2021,10 +2102,14 @@ class DesktopTasksController(
syncQueue,
taskInfo,
displayController,
- context,
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ displayController.getDisplayContext(taskInfo.displayId)
+ } else {
+ context
+ },
taskSurface,
rootTaskDisplayAreaOrganizer,
- dragStartState
+ dragStartState,
)
if (visualIndicator == null) visualIndicator = indicator
return indicator.updateIndicatorType(PointF(inputX, taskTop))
@@ -2039,7 +2124,7 @@ class DesktopTasksController(
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
* @param currentDragBounds the current bounds of where the visible task is (might be actual
- * task bounds or just task leash)
+ * task bounds or just task leash)
* @param validDragArea the bounds of where the task can be dragged within the display.
* @param dragStartBounds the bounds of the task before starting dragging.
*/
@@ -2061,7 +2146,7 @@ class DesktopTasksController(
val indicator = getVisualIndicator() ?: return
val indicatorType =
indicator.updateIndicatorType(
- PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
+ PointF(inputCoordinate.x, currentDragBounds.top.toFloat())
)
when (indicatorType) {
IndicatorType.TO_FULLSCREEN_INDICATOR -> {
@@ -2070,19 +2155,19 @@ class DesktopTasksController(
} else {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN,
)
moveToFullscreenWithAnimation(
taskInfo,
position,
- DesktopModeTransitionSource.TASK_DRAG
+ DesktopModeTransitionSource.TASK_DRAG,
)
}
}
IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT,
)
handleSnapResizingTaskOnDrag(
taskInfo,
@@ -2097,7 +2182,7 @@ class DesktopTasksController(
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT,
)
handleSnapResizingTaskOnDrag(
taskInfo,
@@ -2116,7 +2201,7 @@ class DesktopTasksController(
// If task bounds are outside valid drag area, snap them inward
DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
destinationBounds,
- validDragArea
+ validDragArea,
)
if (destinationBounds == dragStartBounds) {
@@ -2148,8 +2233,9 @@ class DesktopTasksController(
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
- taskbarDesktopTaskListener
- ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(taskInfo.displayId)
+ )
}
/**
@@ -2157,9 +2243,7 @@ class DesktopTasksController(
*
* @param taskInfo the task being dragged.
*/
- fun onDragPositioningCancelThroughStatusBar(
- taskInfo: RunningTaskInfo,
- ) {
+ fun onDragPositioningCancelThroughStatusBar(taskInfo: RunningTaskInfo) {
interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
cancelDragToDesktop(taskInfo)
}
@@ -2183,11 +2267,16 @@ class DesktopTasksController(
when (indicatorType) {
IndicatorType.TO_DESKTOP_INDICATOR -> {
// Start a new jank interaction for the drag release to desktop window animation.
- interactionJankMonitor.begin(taskSurface, context, handler,
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
+ interactionJankMonitor.begin(
+ taskSurface,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
+ "to_desktop",
+ )
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE,
)
finalizeDragToDesktop(taskInfo)
}
@@ -2195,21 +2284,21 @@ class DesktopTasksController(
IndicatorType.TO_FULLSCREEN_INDICATOR -> {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN,
)
cancelDragToDesktop(taskInfo)
}
IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN,
)
requestSplit(taskInfo, leftOrTop = true)
}
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
desktopModeUiEventLogger.log(
taskInfo,
- DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN,
)
requestSplit(taskInfo, leftOrTop = false)
}
@@ -2251,7 +2340,7 @@ class DesktopTasksController(
override fun onUnhandledDrag(
launchIntent: PendingIntent,
dragEvent: DragEvent,
- onFinishCallback: Consumer<Boolean>
+ onFinishCallback: Consumer<Boolean>,
): Boolean {
// TODO(b/320797628): Pass through which display we are dropping onto
if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
@@ -2270,22 +2359,27 @@ class DesktopTasksController(
// window will accept a drag event. This way, we can hide the indicator when we won't
// be handling the transition here, allowing us to display the indicator accurately.
// For now, we create the indicator only on drag end and immediately dispose it.
- val indicatorType = updateVisualIndicator(taskInfo, dragEvent.dragSurface,
- dragEvent.x, dragEvent.y,
- DragStartState.DRAGGED_INTENT)
+ val indicatorType =
+ updateVisualIndicator(
+ taskInfo,
+ dragEvent.dragSurface,
+ dragEvent.x,
+ dragEvent.y,
+ DragStartState.DRAGGED_INTENT,
+ )
releaseVisualIndicator()
- val windowingMode = when (indicatorType) {
- IndicatorType.TO_FULLSCREEN_INDICATOR -> {
- WINDOWING_MODE_FULLSCREEN
- }
- IndicatorType.TO_SPLIT_LEFT_INDICATOR,
- IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
- IndicatorType.TO_DESKTOP_INDICATOR
- -> {
- WINDOWING_MODE_FREEFORM
+ val windowingMode =
+ when (indicatorType) {
+ IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ WINDOWING_MODE_FULLSCREEN
+ }
+ IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ IndicatorType.TO_DESKTOP_INDICATOR -> {
+ WINDOWING_MODE_FREEFORM
+ }
+ else -> error("Invalid indicator type: $indicatorType")
}
- else -> error("Invalid indicator type: $indicatorType")
- }
val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false
val newWindowBounds = Rect()
when (indicatorType) {
@@ -2294,7 +2388,7 @@ class DesktopTasksController(
newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout))
newWindowBounds.offsetTo(
dragEvent.x.toInt() - (newWindowBounds.width() / 2),
- dragEvent.y.toInt()
+ dragEvent.y.toInt(),
)
}
IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
@@ -2342,7 +2436,9 @@ class DesktopTasksController(
// TODO(b/366397912): Support full multi-user mode in Windowing.
override fun onUserChanged(newUserId: Int, userContext: Context) {
+ logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
userId = newUserId
+ taskRepository = userRepositories.getProfile(userId)
desktopTilingDecorViewModel.onUserChange()
}
@@ -2351,12 +2447,15 @@ class DesktopTasksController(
if (!Flags.enableFullyImmersiveInDesktop()) return
val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)
val requestingImmersive = taskInfo.requestingImmersive
- if (inImmersive && !requestingImmersive
- && !RecentsTransitionStateListener.isRunning(recentsTransitionState)) {
+ if (
+ inImmersive &&
+ !requestingImmersive &&
+ !RecentsTransitionStateListener.isRunning(recentsTransitionState)
+ ) {
// Exit immersive if the app is no longer requesting it.
desktopImmersiveController.moveTaskToNonImmersive(
taskInfo,
- DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE
+ DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE,
)
}
}
@@ -2365,6 +2464,7 @@ class DesktopTasksController(
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
DesktopModeStatus.dump(pw, innerPrefix, context)
+ pw.println("${prefix}userId=$userId")
taskRepository.dump(pw, innerPrefix)
}
@@ -2373,7 +2473,7 @@ class DesktopTasksController(
private inner class DesktopModeImpl : DesktopMode {
override fun addVisibleTasksListener(
listener: VisibleTasksListener,
- callbackExecutor: Executor
+ callbackExecutor: Executor,
) {
mainExecutor.execute {
this@DesktopTasksController.addVisibleTasksListener(listener, callbackExecutor)
@@ -2382,7 +2482,7 @@ class DesktopTasksController(
override fun addDesktopGestureExclusionRegionListener(
listener: Consumer<Region>,
- callbackExecutor: Executor
+ callbackExecutor: Executor,
) {
mainExecutor.execute {
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
@@ -2391,8 +2491,9 @@ class DesktopTasksController(
override fun moveFocusedTaskToDesktop(
displayId: Int,
- transitionSource: DesktopModeTransitionSource
+ transitionSource: DesktopModeTransitionSource,
) {
+ logV("moveFocusedTaskToDesktop")
mainExecutor.execute {
this@DesktopTasksController.moveFocusedTaskToDesktop(displayId, transitionSource)
}
@@ -2400,14 +2501,16 @@ class DesktopTasksController(
override fun moveFocusedTaskToFullscreen(
displayId: Int,
- transitionSource: DesktopModeTransitionSource
+ transitionSource: DesktopModeTransitionSource,
) {
+ logV("moveFocusedTaskToFullscreen")
mainExecutor.execute {
this@DesktopTasksController.enterFullscreen(displayId, transitionSource)
}
}
override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) {
+ logV("moveFocusedTaskToStageSplit")
mainExecutor.execute { this@DesktopTasksController.enterSplit(displayId, leftOrTop) }
}
}
@@ -2427,7 +2530,7 @@ class DesktopTasksController(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visibleTasksCount
+ visibleTasksCount,
)
remoteListener.call { l ->
l.onTasksVisibilityChanged(displayId, visibleTasksCount)
@@ -2436,21 +2539,22 @@ class DesktopTasksController(
}
private val taskbarDesktopTaskListener: TaskbarDesktopTaskListener =
- object : TaskbarDesktopTaskListener {
- override fun onTaskbarCornerRoundingUpdate(
- hasTasksRequiringTaskbarRounding: Boolean) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
- "doesAnyTaskRequireTaskbarRounding=%s",
- hasTasksRequiringTaskbarRounding
- )
+ object : TaskbarDesktopTaskListener {
+ override fun onTaskbarCornerRoundingUpdate(
+ hasTasksRequiringTaskbarRounding: Boolean
+ ) {
+ ProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
+ "doesAnyTaskRequireTaskbarRounding=%s",
+ hasTasksRequiringTaskbarRounding,
+ )
- remoteListener.call { l ->
- l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
- }
+ remoteListener.call { l ->
+ l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
}
}
+ }
private val desktopModeEntryExitTransitionListener: DesktopModeEntryExitTransitionListener =
object : DesktopModeEntryExitTransitionListener {
@@ -2458,18 +2562,22 @@ class DesktopTasksController(
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onEnterDesktopModeTransitionStarted transitionTime=%s",
- transitionDuration
+ transitionDuration,
)
- remoteListener.call { l -> l.onEnterDesktopModeTransitionStarted(transitionDuration) }
+ remoteListener.call { l ->
+ l.onEnterDesktopModeTransitionStarted(transitionDuration)
+ }
}
override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"IDesktopModeImpl: onExitDesktopModeTransitionStarted transitionTime=%s",
- transitionDuration
+ transitionDuration,
)
- remoteListener.call { l -> l.onExitDesktopModeTransitionStarted(transitionDuration) }
+ remoteListener.call { l ->
+ l.onExitDesktopModeTransitionStarted(transitionDuration)
+ }
}
}
@@ -2491,7 +2599,7 @@ class DesktopTasksController(
c.taskbarDesktopTaskListener = null
c.desktopModeEnterExitTransitionListener = null
}
- }
+ },
)
}
@@ -2518,8 +2626,10 @@ class DesktopTasksController(
}
override fun hideStashedDesktopApps(displayId: Int) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: hideStashedDesktopApps is deprecated")
+ ProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: hideStashedDesktopApps is deprecated",
+ )
}
override fun getVisibleTaskCount(displayId: Int): Int {
@@ -2528,16 +2638,14 @@ class DesktopTasksController(
controller,
"visibleTaskCount",
{ controller -> result[0] = controller.visibleTaskCount(displayId) },
- true /* blocking */
+ true, /* blocking */
)
return result[0]
}
override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
- executeRemoteCallWithTaskPermission(
- controller,
- "onDesktopSplitSelectAnimComplete"
- ) { c ->
+ executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c
+ ->
c.onDesktopSplitSelectAnimComplete(taskInfo)
}
}
@@ -2571,9 +2679,11 @@ class DesktopTasksController(
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
+
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
+
private fun logW(msg: String, vararg arguments: Any?) {
ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
@@ -2583,6 +2693,10 @@ class DesktopTasksController(
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+ // Timeout used for CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, this is longer than the
+ // default timeout to avoid timing out in the middle of a drag action.
+ private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L)
+
private const val TAG = "DesktopTasksController"
}
@@ -2608,6 +2722,6 @@ class DesktopTasksController(
/** The positions on a screen that a task can snap to. */
enum class SnapPosition {
RIGHT,
- LEFT
+ LEFT,
}
}
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 77af627a948a..635078e68a00 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
@@ -16,14 +16,15 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager
import android.content.Context
import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
-import android.window.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
@@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
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
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
@@ -38,35 +40,37 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver
* 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.
+ * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] is enabled and
+ * [maxTasksLimit] is strictly greater than 0.
*/
-class DesktopTasksLimiter (
- transitions: Transitions,
- private val taskRepository: DesktopRepository,
- private val shellTaskOrganizer: ShellTaskOrganizer,
- private val maxTasksLimit: Int,
- private val interactionJankMonitor: InteractionJankMonitor,
- private val context: Context,
- @ShellMainThread private val handler: Handler,
+class DesktopTasksLimiter(
+ transitions: Transitions,
+ private val desktopUserRepositories: DesktopUserRepositories,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val maxTasksLimit: Int,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ private val context: Context,
+ @ShellMainThread private val handler: Handler,
) {
private val minimizeTransitionObserver = MinimizeTransitionObserver()
- @VisibleForTesting
- val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
+ @VisibleForTesting val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
+
+ private var userId: Int
init {
require(maxTasksLimit > 0) {
"DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit."
}
transitions.registerObserver(minimizeTransitionObserver)
- taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
+ userId = ActivityManager.getCurrentUser()
+ desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover)
logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
}
private data class TaskDetails(
val displayId: Int,
val taskId: Int,
- var transitionInfo: TransitionInfo?
+ var transitionInfo: TransitionInfo?,
)
// TODO(b/333018485): replace this observer when implementing the minimize-animation
@@ -82,8 +86,9 @@ class DesktopTasksLimiter (
transition: IBinder,
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ finishTransaction: SurfaceControl.Transaction,
) {
+ val taskRepository = desktopUserRepositories.current
val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
if (!isTaskReadyForMinimize(info, taskToMinimize)) {
@@ -94,12 +99,13 @@ class DesktopTasksLimiter (
activeTransitionTokensAndTasks[transition] = taskToMinimize
// Save current bounds before minimizing in case we need to restore to it later.
- val boundsBeforeMinimize = info.changes.find { change ->
- change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+ val boundsBeforeMinimize =
+ info.changes
+ .find { change -> change.taskInfo?.taskId == taskToMinimize.taskId }
+ ?.startAbsBounds
taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
- this@DesktopTasksLimiter.minimizeTask(
- taskToMinimize.displayId, taskToMinimize.taskId)
+ this@DesktopTasksLimiter.minimizeTask(taskToMinimize.displayId, taskToMinimize.taskId)
}
/**
@@ -110,10 +116,11 @@ class DesktopTasksLimiter (
*/
private fun isTaskReadyForMinimize(
info: TransitionInfo,
- taskDetails: TaskDetails
+ taskDetails: TaskDetails,
): Boolean {
- val taskChange = info.changes.find { change ->
- change.taskInfo?.taskId == taskDetails.taskId }
+ val taskChange =
+ info.changes.find { change -> change.taskInfo?.taskId == taskDetails.taskId }
+ val taskRepository = desktopUserRepositories.current
if (taskChange == null) return !taskRepository.isVisibleTask(taskDetails.taskId)
return taskChange.mode == TRANSIT_TO_BACK
}
@@ -123,8 +130,10 @@ class DesktopTasksLimiter (
if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
// Begin minimize window CUJ instrumentation.
interactionJankMonitor.begin(
- mActiveTaskDetails.transitionInfo?.rootLeash, context, handler,
- CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
+ mActiveTaskDetails.transitionInfo?.rootLeash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
)
}
}
@@ -151,7 +160,8 @@ class DesktopTasksLimiter (
}
@VisibleForTesting
- inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
+ inner class LeftoverMinimizedTasksRemover :
+ DesktopRepository.ActiveTasksListener, UserChangeListener {
override fun onActiveTasksChanged(displayId: Int) {
// If back navigation is enabled, we shouldn't remove the leftover tasks
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
@@ -161,6 +171,7 @@ class DesktopTasksLimiter (
}
fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
+ val taskRepository = desktopUserRepositories.current
if (taskRepository.getExpandedTasksOrdered(displayId).isNotEmpty()) return
val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
if (remainingMinimizedTasks.isEmpty()) return
@@ -173,39 +184,46 @@ class DesktopTasksLimiter (
}
}
}
+
+ override fun onUserChanged(newUserId: Int, userContext: Context) {
+ // Removes active task listener for the previous repository
+ desktopUserRepositories.getProfile(userId).removeActiveTasksListener(this)
+
+ // Sets active listener for the current repository.
+ userId = newUserId
+ desktopUserRepositories.getProfile(newUserId).addActiveTaskListener(this)
+ }
}
/**
* Mark task with [taskId] on [displayId] as minimized.
*
- * This should be after the corresponding transition has finished so we don't
- * minimize the task if the transition fails.
+ * This should be after the corresponding transition has finished so we don't minimize the task
+ * if the transition fails.
*/
private fun minimizeTask(displayId: Int, taskId: Int) {
logV("Minimize taskId=%d, displayId=%d", taskId, displayId)
+ val taskRepository = desktopUserRepositories.current
taskRepository.minimizeTask(displayId, taskId)
}
/**
- * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task
- * limit, returning the task to minimize.
+ * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task limit,
+ * returning the task to minimize.
*/
fun addAndGetMinimizeTaskChanges(
- displayId: Int,
- wct: WindowContainerTransaction,
- newFrontTaskId: Int,
+ displayId: Int,
+ wct: WindowContainerTransaction,
+ newFrontTaskId: Int,
): Int? {
logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId)
-
+ val taskRepository = desktopUserRepositories.current
val taskIdToMinimize =
- getTaskIdToMinimize(
- taskRepository.getExpandedTasksOrdered(displayId),
- newFrontTaskId
- )
+ getTaskIdToMinimize(taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId)
// If it's a running task, reorder it to back.
- taskIdToMinimize?.let { shellTaskOrganizer.getRunningTaskInfo(it) }?.let {
- wct.reorder(it.token, false /* onTop */)
- }
+ taskIdToMinimize
+ ?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
+ ?.let { wct.reorder(it.token, false /* onTop */) }
return taskIdToMinimize
}
@@ -215,20 +233,19 @@ class DesktopTasksLimiter (
*/
fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) {
minimizeTransitionObserver.addPendingTransitionToken(
- transition, TaskDetails(displayId, taskId, transitionInfo = null))
+ transition,
+ TaskDetails(displayId, taskId, transitionInfo = null),
+ )
}
/**
- * Returns the minimized task from the list of visible tasks ordered from front to back with
- * the new task placed in front of other tasks.
+ * Returns the minimized task from the list of visible tasks ordered from front to back with the
+ * new task placed in front of other tasks.
*/
- fun getTaskIdToMinimize(
- visibleOrderedTasks: List<Int>,
- newTaskIdInFront: Int? = null
- ): Int? {
+ fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>, newTaskIdInFront: Int? = null): Int? {
return getTaskIdToMinimize(
- createOrderedTaskListWithGivenTaskInFront(
- visibleOrderedTasks, newTaskIdInFront))
+ createOrderedTaskListWithGivenTaskInFront(visibleOrderedTasks, newTaskIdInFront)
+ )
}
/** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
@@ -242,16 +259,16 @@ class DesktopTasksLimiter (
}
private fun createOrderedTaskListWithGivenTaskInFront(
- existingTaskIdsOrderedFrontToBack: List<Int>,
- newTaskId: Int?
+ existingTaskIdsOrderedFrontToBack: List<Int>,
+ newTaskId: Int?,
): List<Int> {
return if (newTaskId == null) existingTaskIdsOrderedFrontToBack
- else listOf(newTaskId) +
+ else
+ listOf(newTaskId) +
existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId }
}
- @VisibleForTesting
- fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
+ @VisibleForTesting fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
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/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index c39c715e685c..9625b71ad3cb 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.IBinder
@@ -23,12 +24,13 @@ import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
-import android.window.TransitionInfo
-import android.window.WindowContainerTransaction
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
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.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
@@ -43,19 +45,22 @@ import com.android.wm.shell.transition.Transitions
*/
class DesktopTasksTransitionObserver(
private val context: Context,
- private val desktopRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
- shellInit: ShellInit
+ private val backAnimationController: BackAnimationController,
+ shellInit: ShellInit,
) : Transitions.TransitionObserver {
private var transitionToCloseWallpaper: IBinder? = null
+ private var currentProfileId: Int
init {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
shellInit.addInitCallback(::onInit, this)
}
+ currentProfileId = ActivityManager.getCurrentUser()
}
fun onInit() {
@@ -67,7 +72,7 @@ class DesktopTasksTransitionObserver(
transition: IBinder,
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
@@ -89,8 +94,11 @@ class DesktopTasksTransitionObserver(
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) continue
- if (desktopRepository.isActiveTask(taskInfo.taskId) &&
- taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) {
+ val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+ if (
+ desktopRepository.isActiveTask(taskInfo.taskId) &&
+ taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+ ) {
desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
}
}
@@ -105,35 +113,103 @@ class DesktopTasksTransitionObserver(
if (taskInfo == null || taskInfo.taskId == -1) {
continue
}
-
+ val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
- if (visibleTaskCount > 0 &&
- change.mode == TRANSIT_TO_BACK &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (
+ visibleTaskCount > 0 &&
+ change.mode == TRANSIT_TO_BACK &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
desktopMixedTransitionHandler.addPendingMixedTransition(
DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
- transition, taskInfo.taskId, visibleTaskCount == 1))
+ transition,
+ taskInfo.taskId,
+ visibleTaskCount == 1,
+ )
+ )
+ }
+ }
+ } else if (info.type == TRANSIT_CLOSE) {
+ // In some cases app will be closing as a result of back navigation but we would like
+ // to minimize. Mark the task closing as minimized.
+ var hasWallpaperClosing = false
+ var minimizingTask: Int? = null
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) continue
+ if (change.mode != TRANSIT_CLOSE) continue
+
+ if (minimizingTask == null) {
+ minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
+ }
+
+ if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
+ hasWallpaperClosing = true
}
}
+
+ if (minimizingTask == null) return
+ // If the transition has wallpaper closing, it means we are moving out of desktop.
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition,
+ minimizingTask,
+ isLastTask = hasWallpaperClosing,
+ )
+ )
+ }
+ }
+
+ /**
+ * Given this a closing task in a closing transition, a task is assumed to be closed by back
+ * navigation if:
+ * 1) Desktop mode is visible.
+ * 2) Task is in freeform.
+ * 3) Task is the latest task that the back gesture is triggered on.
+ * 4) It's not marked as a closing task as a result of closing it by the app header.
+ *
+ * This doesn't necessarily mean all the cases are because of back navigation but those cases
+ * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
+ * closing it will minimize it here.
+ */
+ private fun getMinimizingTaskForClosingTransition(
+ taskInfo: ActivityManager.RunningTaskInfo
+ ): Int? {
+ val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+ val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
+ if (
+ visibleTaskCount > 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+ backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
+ !desktopRepository.isClosingTask(taskInfo.taskId)
+ ) {
+ desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ return taskInfo.taskId
}
+ return null
}
private fun removeWallpaperOnLastTaskClosingIfNeeded(
transition: IBinder,
- info: TransitionInfo
+ info: TransitionInfo,
) {
+ // TODO: 380868195 - Smooth animation for wallpaper activity closing just by itself
for (change in info.changes) {
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) {
continue
}
- if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 &&
- change.mode == TRANSIT_CLOSE &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
- desktopRepository.wallpaperActivityToken != null) {
+ val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+ if (
+ desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 &&
+ change.mode == TRANSIT_CLOSE &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+ desktopRepository.wallpaperActivityToken != null
+ ) {
transitionToCloseWallpaper = transition
+ currentProfileId = taskInfo.userId
}
}
}
@@ -150,11 +226,13 @@ class DesktopTasksTransitionObserver(
// TODO: b/332682201 Update repository state
if (transitionToCloseWallpaper == transition) {
// TODO: b/362469671 - Handle merging the animation when desktop is also closing.
+ val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken ->
transitions.startTransition(
TRANSIT_CLOSE,
WindowContainerTransaction().removeTask(wallpaperActivityToken),
- null)
+ null,
+ )
}
transitionToCloseWallpaper = null
}
@@ -167,6 +245,7 @@ class DesktopTasksTransitionObserver(
info.changes.forEach { change ->
change.taskInfo?.let { taskInfo ->
if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
+ val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
when (change.mode) {
WindowManager.TRANSIT_OPEN -> {
desktopRepository.wallpaperActivityToken = taskInfo.token
@@ -175,10 +254,10 @@ class DesktopTasksTransitionObserver(
// task.
shellTaskOrganizer.applyTransaction(
WindowContainerTransaction()
- .setTaskTrimmableFromRecents(taskInfo.token, false))
+ .setTaskTrimmableFromRecents(taskInfo.token, false)
+ )
}
- TRANSIT_CLOSE ->
- desktopRepository.wallpaperActivityToken = null
+ TRANSIT_CLOSE -> desktopRepository.wallpaperActivityToken = null
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
new file mode 100644
index 000000000000..e5f52839d9f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.util.SparseArray
+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.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
+import kotlinx.coroutines.CoroutineScope
+
+/** Manages per-user DesktopRepository instances. */
+class DesktopUserRepositories(
+ context: Context,
+ shellInit: ShellInit,
+ private val persistentRepository: DesktopPersistentRepository,
+ private val repositoryInitializer: DesktopRepositoryInitializer,
+ @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+ userManager: UserManager,
+) : UserChangeListener {
+ private var userId: Int
+ private var userIdToProfileIdsMap: MutableMap<Int, List<Int>> = mutableMapOf()
+
+ // TODO(b/357060209): Add caching for this logic to improve efficiency.
+ val current: DesktopRepository
+ get() = desktopRepoByUserId.getOrCreate(userId)
+
+ private val desktopRepoByUserId =
+ object : SparseArray<DesktopRepository>() {
+ /** Gets [DesktopRepository] for existing [userId] or creates a new one. */
+ fun getOrCreate(userId: Int): DesktopRepository =
+ this[userId]
+ ?: DesktopRepository(persistentRepository, mainCoroutineScope, userId).also {
+ this[userId] = it
+ }
+ }
+
+ init {
+ userId = ActivityManager.getCurrentUser()
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
+ shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+ }
+ if (Flags.enableDesktopWindowingHsum()) {
+ userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
+ }
+ }
+
+ private fun initRepoFromPersistentStorage() {
+ repositoryInitializer.initialize(this)
+ }
+
+ /** Returns [DesktopRepository] for the parent user id. */
+ fun getProfile(profileId: Int): DesktopRepository {
+ if (Flags.enableDesktopWindowingHsum()) {
+ for ((uid, profileIds) in userIdToProfileIdsMap) {
+ if (profileId in profileIds) {
+ return desktopRepoByUserId.getOrCreate(uid)
+ }
+ }
+ }
+ return desktopRepoByUserId.getOrCreate(profileId)
+ }
+
+ override fun onUserChanged(newUserId: Int, userContext: Context) {
+ logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
+ userId = newUserId
+ }
+
+ override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) {
+ logD("onUserProfilesChanged profiles=%s", profiles.toString())
+ if (Flags.enableDesktopWindowingHsum()) {
+ // TODO(b/366397912): Remove all persisted profile data when the profile changes.
+ userIdToProfileIdsMap[userId] = profiles.map { it.id }
+ }
+ }
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopUserRepositories"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index 909a06604382..fd39becc2715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -49,7 +49,6 @@ class DesktopWallpaperActivity : Activity() {
taskInfo.baseIntent.component?.let(::isWallpaperComponent) ?: false
@JvmStatic
- fun isWallpaperComponent(component: ComponentName) =
- component == wallpaperActivityComponent
+ fun isWallpaperComponent(component: ComponentName) = component == wallpaperActivityComponent
}
}
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 d7d55195d4cf..d23c9d0b8ffd 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
@@ -19,10 +19,10 @@ import android.content.Intent
import android.content.Intent.FILL_IN_COMPONENT
import android.graphics.PointF
import android.graphics.Rect
-import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
import android.os.SystemProperties
+import android.os.UserHandle
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.window.TransitionInfo
@@ -109,13 +109,13 @@ sealed class DragToDesktopTransitionHandler(
* after one of the "end" or "cancel" transitions is merged into this transition.
*/
fun startDragToDesktopTransition(
- taskId: Int,
+ taskInfo: RunningTaskInfo,
dragToDesktopAnimator: MoveToDesktopAnimator,
) {
if (inProgress) {
ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: Drag to desktop transition already in progress."
+ "DragToDesktop: Drag to desktop transition already in progress.",
)
return
}
@@ -127,35 +127,41 @@ sealed class DragToDesktopTransitionHandler(
pendingIntentCreatorBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
+ val taskUser = UserHandle.of(taskInfo.userId)
val pendingIntent =
- PendingIntent.getActivity(
- context,
+ PendingIntent.getActivityAsUser(
+ context.createContextAsUser(taskUser, /* flags= */ 0),
0 /* requestCode */,
launchHomeIntent,
FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
- options.toBundle()
+ options.toBundle(),
+ taskUser,
)
val wct = WindowContainerTransaction()
- wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The app that is being dragged into desktop mode might cause new transitions, make this
+ // launch transient to make sure those transitions can execute in parallel and thus won't
+ // block the end-drag transition.
+ val intentOptions = ActivityOptions.makeBasic().setTransientLaunch()
+ wct.sendPendingIntent(pendingIntent, launchHomeIntent, intentOptions.toBundle())
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
transitionState =
- if (isSplitTask(taskId)) {
+ if (isSplitTask(taskInfo.taskId)) {
val otherTask =
- getOtherSplitTask(taskId)
+ getOtherSplitTask(taskInfo.taskId)
?: throw IllegalStateException("Expected split task to have a counterpart.")
TransitionState.FromSplit(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
- otherSplitTask = otherTask
+ otherSplitTask = otherTask,
)
} else {
TransitionState.FromFullscreen(
- draggedTaskId = taskId,
+ draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
- startTransitionToken = startTransitionToken
+ startTransitionToken = startTransitionToken,
)
}
}
@@ -244,7 +250,7 @@ sealed class DragToDesktopTransitionHandler(
/** Calculate the bounds of a scaled task, then use those bounds to request split select. */
private fun requestSplitFromScaledTask(
@SplitPosition splitPosition: Int,
- wct: WindowContainerTransaction
+ wct: WindowContainerTransaction,
) {
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
@@ -259,7 +265,7 @@ sealed class DragToDesktopTransitionHandler(
dragPosition.x.toInt(),
dragPosition.y.toInt(),
(dragPosition.x + scaledWidth).toInt(),
- (dragPosition.y + scaledHeight).toInt()
+ (dragPosition.y + scaledHeight).toInt(),
)
requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
}
@@ -268,14 +274,14 @@ sealed class DragToDesktopTransitionHandler(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
@SplitPosition splitPosition: Int,
- taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds)
+ taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
) {
// Prepare to exit split in order to enter split select.
if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
splitScreenController.prepareExitSplitScreen(
wct,
splitScreenController.getStageOfTask(taskInfo.taskId),
- SplitScreenController.EXIT_REASON_DESKTOP_MODE
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE,
)
splitScreenController.transitionHandler.onSplitToDesktop()
}
@@ -289,7 +295,7 @@ sealed class DragToDesktopTransitionHandler(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
val state = requireTransitionState()
@@ -387,7 +393,7 @@ sealed class DragToDesktopTransitionHandler(
taskDisplayAreaOrganizer.reparentToDisplayArea(
change.endDisplayId,
change.leash,
- startTransaction
+ startTransaction,
)
val bounds = change.endAbsBounds
startTransaction.apply {
@@ -454,7 +460,7 @@ sealed class DragToDesktopTransitionHandler(
info: TransitionInfo,
t: SurfaceControl.Transaction,
mergeTarget: IBinder,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
) {
val state = requireTransitionState()
// We don't want to merge the split select animation if that's what we requested.
@@ -483,7 +489,7 @@ sealed class DragToDesktopTransitionHandler(
setupEndDragToDesktop(
info,
startTransaction = t,
- finishTransaction = startTransactionFinishT
+ finishTransaction = startTransactionFinishT,
)
// Call finishCallback to merge animation before startTransitionFinishCb is called
finishCallback.onTransitionFinished(null /* wct */)
@@ -503,7 +509,7 @@ sealed class DragToDesktopTransitionHandler(
protected open fun setupEndDragToDesktop(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ finishTransaction: SurfaceControl.Transaction,
) {
val state = requireTransitionState()
val freeformTaskChanges = mutableListOf<Change>()
@@ -545,7 +551,7 @@ sealed class DragToDesktopTransitionHandler(
protected open fun animateEndDragToDesktop(
startTransaction: SurfaceControl.Transaction,
- startTransitionFinishCb: Transitions.TransitionFinishCallback
+ startTransitionFinishCb: Transitions.TransitionFinishCallback,
) {
val state = requireTransitionState()
val draggedTaskChange =
@@ -568,7 +574,7 @@ sealed class DragToDesktopTransitionHandler(
startPosition.x.toInt(),
startPosition.y.toInt(),
startPosition.x.toInt() + unscaledStartWidth,
- startPosition.y.toInt() + unscaledStartHeight
+ startPosition.y.toInt() + unscaledStartHeight,
)
dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
@@ -578,7 +584,7 @@ sealed class DragToDesktopTransitionHandler(
onTaskResizeAnimationListener.onAnimationStart(
state.draggedTaskId,
startTransaction,
- unscaledStartBounds
+ unscaledStartBounds,
)
val tx: SurfaceControl.Transaction = transactionSupplier.get()
ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
@@ -594,21 +600,21 @@ sealed class DragToDesktopTransitionHandler(
setPosition(
draggedTaskLeash,
animBounds.left.toFloat(),
- animBounds.top.toFloat()
+ animBounds.top.toFloat(),
)
setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
}
onTaskResizeAnimationListener.onBoundsChange(
state.draggedTaskId,
tx,
- animBounds
+ animBounds,
)
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
- startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+ startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
clearState()
interactionJankMonitor.end(
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -622,7 +628,7 @@ sealed class DragToDesktopTransitionHandler(
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? {
// Only handle transitions started from shell.
return null
@@ -631,7 +637,7 @@ sealed class DragToDesktopTransitionHandler(
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
- finishTransaction: SurfaceControl.Transaction?
+ finishTransaction: SurfaceControl.Transaction?,
) {
val state = transitionState ?: return
if (!aborted) {
@@ -640,15 +646,13 @@ sealed class DragToDesktopTransitionHandler(
if (state.startTransitionToken == transition) {
ProtoLog.v(
ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "DragToDesktop: onTransitionConsumed() start transition aborted"
+ "DragToDesktop: onTransitionConsumed() start transition aborted",
)
state.startAborted = true
// The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction.
interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
} else if (state.cancelTransitionToken == transition) {
- state.draggedTaskChange?.leash?.let {
- state.startTransitionFinishTransaction?.show(it)
- }
+ state.draggedTaskChange?.leash?.let { state.startTransitionFinishTransaction?.show(it) }
state.startTransitionFinishCb?.onTransitionFinished(null /* wct */)
clearState()
} else {
@@ -724,7 +728,7 @@ sealed class DragToDesktopTransitionHandler(
private fun restoreWindowOrder(
wct: WindowContainerTransaction,
- state: TransitionState = requireTransitionState()
+ state: TransitionState = requireTransitionState(),
) {
when (state) {
is TransitionState.FromFullscreen -> {
@@ -831,7 +835,7 @@ sealed class DragToDesktopTransitionHandler(
override var surfaceLayers: DragToDesktopLayers? = null,
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
- var otherRootChanges: MutableList<Change> = mutableListOf()
+ var otherRootChanges: MutableList<Change> = mutableListOf(),
) : TransitionState()
data class FromSplit(
@@ -848,7 +852,7 @@ sealed class DragToDesktopTransitionHandler(
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
var splitRootChange: Change? = null,
- var otherSplitTask: Int
+ var otherSplitTask: Int,
) : TransitionState()
}
@@ -861,7 +865,7 @@ sealed class DragToDesktopTransitionHandler(
/** A cancel event where the task will request to enter split on the left side. */
CANCEL_SPLIT_LEFT,
/** A cancel event where the task will request to enter split on the right side. */
- CANCEL_SPLIT_RIGHT
+ CANCEL_SPLIT_RIGHT,
}
companion object {
@@ -888,7 +892,7 @@ constructor(
transitions,
taskDisplayAreaOrganizer,
interactionJankMonitor,
- transactionSupplier
+ transactionSupplier,
) {
/**
@@ -903,7 +907,7 @@ constructor(
topAppLayer = info.changes.size,
topHomeLayer = info.changes.size * 2,
topWallpaperLayer = info.changes.size * 3,
- dragLayer = info.changes.size * 3
+ dragLayer = info.changes.size * 3,
)
}
@@ -924,7 +928,7 @@ constructor(
transitions,
taskDisplayAreaOrganizer,
interactionJankMonitor,
- transactionSupplier
+ transactionSupplier,
) {
private val positionSpringConfig =
@@ -945,13 +949,13 @@ constructor(
topAppLayer = -1,
topHomeLayer = info.changes.size - 1,
topWallpaperLayer = info.changes.size * 2 - 1,
- dragLayer = info.changes.size * 2
+ dragLayer = info.changes.size * 2,
)
override fun setupEndDragToDesktop(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ finishTransaction: SurfaceControl.Transaction,
) {
super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
@@ -974,7 +978,7 @@ constructor(
override fun animateEndDragToDesktop(
startTransaction: SurfaceControl.Transaction,
- startTransitionFinishCb: Transitions.TransitionFinishCallback
+ startTransitionFinishCb: Transitions.TransitionFinishCallback,
) {
val state = requireTransitionState()
val draggedTaskChange =
@@ -1002,7 +1006,7 @@ constructor(
onTaskResizeAnimationListener.onAnimationStart(
state.draggedTaskId,
startTransaction,
- startBoundsWithOffset
+ startBoundsWithOffset,
)
val tx: SurfaceControl.Transaction = transactionSupplier.get()
@@ -1011,13 +1015,13 @@ constructor(
FloatProperties.RECT_X,
endBounds.left.toFloat(),
currentVelocity.x,
- positionSpringConfig
+ positionSpringConfig,
)
.spring(
FloatProperties.RECT_Y,
endBounds.top.toFloat(),
currentVelocity.y,
- positionSpringConfig
+ positionSpringConfig,
)
.spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
@@ -1050,7 +1054,7 @@ constructor(
setPosition(
draggedTaskLeash,
animBounds.left.toFloat(),
- animBounds.top.toFloat()
+ animBounds.top.toFloat(),
)
// Update freeform tasks
freeformTaskChanges.forEach {
@@ -1069,7 +1073,7 @@ constructor(
}
.withEndActions({
onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
- startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+ startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
clearState()
interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
})
@@ -1094,7 +1098,7 @@ constructor(
propertyValue(
"position_damping_ratio",
scale = 100f,
- default = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ default = SpringForce.DAMPING_RATIO_LOW_BOUNCY,
)
/** The spring force stiffness used to resize the window into the final bounds. */
@@ -1106,7 +1110,7 @@ constructor(
propertyValue(
"size_damping_ratio",
scale = 100f,
- default = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ default = SpringForce.DAMPING_RATIO_NO_BOUNCY,
)
/** Drag to desktop transition system properties group. */
@@ -1123,7 +1127,7 @@ constructor(
fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float =
SystemProperties.getInt(
/* key= */ "$SYSTEM_PROPERTIES_GROUP.$name",
- /* def= */ (default * scale).toInt()
+ /* def= */ (default * scale).toInt(),
) / scale
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 4e08d106052a..3edeecbdca5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -30,13 +30,14 @@ import java.util.function.Supplier
/** Animates the task surface moving from its current drag position to its pre-drag position. */
class ReturnToDragStartAnimator(
private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
) {
private var boundsAnimator: Animator? = null
private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
- constructor(interactionJankMonitor: InteractionJankMonitor) :
- this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+ constructor(
+ interactionJankMonitor: InteractionJankMonitor
+ ) : this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/** Sets a listener for the start and end of the reposition animation. */
fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -65,7 +66,7 @@ class ReturnToDragStartAnimator(
.setPosition(
taskSurface,
startBounds.left.toFloat(),
- startBounds.top.toFloat()
+ startBounds.top.toFloat(),
)
.show(taskSurface)
.apply()
@@ -77,7 +78,7 @@ class ReturnToDragStartAnimator(
.setPosition(
taskSurface,
endBounds.left.toFloat(),
- endBounds.top.toFloat()
+ endBounds.top.toFloat(),
)
.show(taskSurface)
.apply()
@@ -85,7 +86,7 @@ class ReturnToDragStartAnimator(
boundsAnimator = null
doOnEnd?.invoke()
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
- }
+ },
)
addUpdateListener { anim ->
val rect = anim.animatedValue as Rect
@@ -100,4 +101,4 @@ class ReturnToDragStartAnimator(
companion object {
const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L
}
-} \ No newline at end of file
+}
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 6df3302f47a5..e683f62644c4 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
@@ -39,7 +39,7 @@ import java.util.function.Supplier
class ToggleResizeDesktopTaskTransitionHandler(
private val transitions: Transitions,
private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
- private val interactionJankMonitor: InteractionJankMonitor
+ private val interactionJankMonitor: InteractionJankMonitor,
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
@@ -50,16 +50,16 @@ class ToggleResizeDesktopTaskTransitionHandler(
constructor(
transitions: Transitions,
- interactionJankMonitor: InteractionJankMonitor
+ interactionJankMonitor: InteractionJankMonitor,
) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/**
* Starts a quick resize transition.
*
- * @param wct WindowContainerTransaction that will update core about the task changes applied
- * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the
- * bounds of the actual task). This is provided so that the animation
- * resizing can begin where the task leash currently is for smoother UX.
+ * @param wct WindowContainerTransaction that will update core about the task changes applied
+ * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the
+ * 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) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
@@ -75,7 +75,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
val change = findRelevantChange(info)
val leash = change.leash
@@ -95,7 +95,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
.setPosition(
leash,
startBounds.left.toFloat(),
- startBounds.top.toFloat()
+ startBounds.top.toFloat(),
)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
@@ -110,7 +110,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
.setPosition(
leash,
endBounds.left.toFloat(),
- endBounds.top.toFloat()
+ endBounds.top.toFloat(),
)
.setWindowCrop(leash, endBounds.width(), endBounds.height())
.show(leash)
@@ -121,7 +121,7 @@ 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)
- }
+ },
)
addUpdateListener { anim ->
val rect = anim.animatedValue as Rect
@@ -138,7 +138,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? {
return null
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
index 8bfcca093855..131b7485bfa5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
@@ -27,23 +27,22 @@ import kotlinx.coroutines.flow.StateFlow
/** Repository to observe caption state. */
class WindowDecorCaptionHandleRepository {
- private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
- /** Observer for app handle state changes. */
- val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
- private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
- /** Observer for App-to-Web usage. */
- val appToWebUsageFlow = _appToWebUsageFlow
+ private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
+ /** Observer for app handle state changes. */
+ val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
+ private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
+ /** Observer for App-to-Web usage. */
+ val appToWebUsageFlow = _appToWebUsageFlow
+ /** Notifies [captionStateFlow] if there is a change to caption state. */
+ fun notifyCaptionChanged(captionState: CaptionState) {
+ _captionStateFlow.value = captionState
+ }
- /** Notifies [captionStateFlow] if there is a change to caption state. */
- fun notifyCaptionChanged(captionState: CaptionState) {
- _captionStateFlow.value = captionState
- }
-
- /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
- fun onAppToWebUsage() {
- _appToWebUsageFlow.tryEmit(Unit)
- }
+ /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
+ fun onAppToWebUsage() {
+ _appToWebUsageFlow.tryEmit(Unit)
+ }
}
/**
@@ -54,20 +53,20 @@ class WindowDecorCaptionHandleRepository {
* * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
* * [NoCaption]: Signifying that no caption handle is currently visible on the device.
*/
-sealed class CaptionState{
- data class AppHandle(
- val runningTaskInfo: RunningTaskInfo,
- val isHandleMenuExpanded: Boolean,
- val globalAppHandleBounds: Rect,
- val isCapturedLinkAvailable: Boolean
- ) : CaptionState()
+sealed class CaptionState {
+ data class AppHandle(
+ val runningTaskInfo: RunningTaskInfo,
+ val isHandleMenuExpanded: Boolean,
+ val globalAppHandleBounds: Rect,
+ val isCapturedLinkAvailable: Boolean,
+ ) : CaptionState()
- data class AppHeader(
- val runningTaskInfo: RunningTaskInfo,
- val isHeaderMenuExpanded: Boolean,
- val globalAppChipBounds: Rect,
- val isCapturedLinkAvailable: Boolean
- ) : CaptionState()
+ data class AppHeader(
+ val runningTaskInfo: RunningTaskInfo,
+ val isHeaderMenuExpanded: Boolean,
+ val globalAppChipBounds: Rect,
+ val isCapturedLinkAvailable: Boolean,
+ ) : CaptionState()
- data object NoCaption : CaptionState()
+ data object NoCaption : CaptionState()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
new file mode 100644
index 000000000000..f6ebf7221e82
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.desktopmode.common
+
+import android.graphics.Rect
+import com.android.internal.jank.Cuj
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source
+
+/** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */
+data class ToggleTaskSizeInteraction
+@JvmOverloads
+constructor(
+ val direction: Direction,
+ val source: Source,
+ val inputMethod: InputMethod,
+ val animationStartBounds: Rect? = null,
+) {
+ constructor(
+ isMaximized: Boolean,
+ source: Source,
+ inputMethod: InputMethod,
+ ) : this(
+ direction = if (isMaximized) Direction.RESTORE else Direction.MAXIMIZE,
+ source = source,
+ inputMethod = inputMethod,
+ )
+
+ val jankTag: String? =
+ when (source) {
+ Source.HEADER_BUTTON_TO_MAXIMIZE -> "caption_bar_button"
+ Source.HEADER_BUTTON_TO_RESTORE -> "caption_bar_button"
+ Source.KEYBOARD_SHORTCUT -> null
+ Source.HEADER_DRAG_TO_TOP -> null
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE -> "maximize_menu"
+ Source.MAXIMIZE_MENU_TO_RESTORE -> "maximize_menu"
+ Source.DOUBLE_TAP_TO_MAXIMIZE -> "double_tap"
+ Source.DOUBLE_TAP_TO_RESTORE -> "double_tap"
+ }
+ val uiEvent: DesktopUiEventEnum? =
+ when (source) {
+ Source.HEADER_BUTTON_TO_MAXIMIZE ->
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP
+ Source.HEADER_BUTTON_TO_RESTORE -> DesktopUiEventEnum.DESKTOP_WINDOW_RESTORE_BUTTON_TAP
+ Source.KEYBOARD_SHORTCUT -> null
+ Source.HEADER_DRAG_TO_TOP -> null
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE -> {
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE
+ }
+ Source.MAXIMIZE_MENU_TO_RESTORE -> {
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE
+ }
+ Source.DOUBLE_TAP_TO_MAXIMIZE -> {
+ DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE
+ }
+ Source.DOUBLE_TAP_TO_RESTORE -> {
+ DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE
+ }
+ }
+ val resizeTrigger =
+ when (source) {
+ Source.HEADER_BUTTON_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_BUTTON
+ Source.HEADER_BUTTON_TO_RESTORE -> ResizeTrigger.MAXIMIZE_BUTTON
+ Source.KEYBOARD_SHORTCUT -> ResizeTrigger.UNKNOWN_RESIZE_TRIGGER
+ Source.HEADER_DRAG_TO_TOP -> ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_MENU
+ Source.MAXIMIZE_MENU_TO_RESTORE -> ResizeTrigger.MAXIMIZE_MENU
+ Source.DOUBLE_TAP_TO_MAXIMIZE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+ Source.DOUBLE_TAP_TO_RESTORE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+ }
+ val cujTracing: Int? =
+ when (source) {
+ Source.HEADER_BUTTON_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW
+ Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
+ Source.KEYBOARD_SHORTCUT -> null
+ Source.HEADER_DRAG_TO_TOP -> null
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null
+ Source.MAXIMIZE_MENU_TO_RESTORE -> null
+ Source.DOUBLE_TAP_TO_MAXIMIZE -> null
+ Source.DOUBLE_TAP_TO_RESTORE -> null
+ }
+
+ /** The direction to which the task is being resized. */
+ enum class Direction {
+ MAXIMIZE,
+ RESTORE,
+ }
+
+ /** The user interaction source. */
+ enum class Source {
+ HEADER_BUTTON_TO_MAXIMIZE,
+ HEADER_BUTTON_TO_RESTORE,
+ KEYBOARD_SHORTCUT,
+ HEADER_DRAG_TO_TOP,
+ MAXIMIZE_MENU_TO_MAXIMIZE,
+ MAXIMIZE_MENU_TO_RESTORE,
+ DOUBLE_TAP_TO_MAXIMIZE,
+ DOUBLE_TAP_TO_RESTORE,
+ }
+
+ /**
+ * Temporary sources for interactions that should be broken into more specific sources, for
+ * example, the header button click should use [Source.HEADER_BUTTON_TO_MAXIMIZE] and
+ * [Source.HEADER_BUTTON_TO_RESTORE].
+ *
+ * TODO: b/341320112 - break these out into different [Source]s.
+ */
+ enum class AmbiguousSource {
+ HEADER_BUTTON,
+ MAXIMIZE_MENU,
+ DOUBLE_TAP,
+ }
+}
+
+/** Returns the non-ambiguous [Source] based on the maximized state of the task. */
+fun AmbiguousSource.toSource(isMaximized: Boolean): Source {
+ return when (this) {
+ AmbiguousSource.HEADER_BUTTON ->
+ if (isMaximized) {
+ Source.HEADER_BUTTON_TO_RESTORE
+ } else {
+ Source.HEADER_BUTTON_TO_MAXIMIZE
+ }
+ AmbiguousSource.MAXIMIZE_MENU ->
+ if (isMaximized) {
+ Source.MAXIMIZE_MENU_TO_RESTORE
+ } else {
+ Source.MAXIMIZE_MENU_TO_MAXIMIZE
+ }
+ AmbiguousSource.DOUBLE_TAP ->
+ if (isMaximized) {
+ Source.DOUBLE_TAP_TO_RESTORE
+ } else {
+ Source.DOUBLE_TAP_TO_MAXIMIZE
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
index 826de08557bd..a428ce18a49e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
@@ -29,7 +29,7 @@ import com.android.app.animation.Interpolators
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil.isClosingMode
import com.android.wm.shell.shared.TransitionUtil.isClosingType
@@ -46,7 +46,7 @@ class SystemModalsTransitionHandler(
private val animExecutor: ShellExecutor,
private val shellInit: ShellInit,
private val transitions: Transitions,
- private val desktopRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
) : TransitionHandler {
private val showingSystemModalsIds = mutableSetOf<Int>()
@@ -156,7 +156,7 @@ class SystemModalsTransitionHandler(
}
private fun isDesktopModeShowing(displayId: Int): Boolean =
- desktopRepository.getVisibleTaskCount(displayId) > 0
+ desktopUserRepositories.current.getVisibleTaskCount(displayId) > 0
override fun handleRequest(
transition: IBinder,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
index bfe1b12c9605..ac0a6275cfbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -116,7 +116,12 @@ class AppToWebEducationController(
}
private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
- if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block()
+ if (
+ canEnterDesktopMode(context) &&
+ Flags.enableDesktopWindowingAppToWebEducationIntegration()
+ ) {
+ block()
+ }
}
private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
index 7554cbb96606..4298bd25dc95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
@@ -43,7 +43,7 @@ class DesktopWindowLimitRemoteHandler(
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
remoteTransition: RemoteTransition,
private val taskIdToMinimize: Int,
- ) : TransitionHandler {
+) : TransitionHandler {
private val oneShotRemoteHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
private var transition: IBinder? = null
@@ -56,7 +56,7 @@ class DesktopWindowLimitRemoteHandler(
override fun handleRequest(
transition: IBinder,
- request: TransitionRequestInfo
+ request: TransitionRequestInfo,
): WindowContainerTransaction? {
this.transition = transition
return oneShotRemoteHandler.handleRequest(transition, request)
@@ -67,7 +67,7 @@ class DesktopWindowLimitRemoteHandler(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
if (transition != this.transition) return false
val minimizeChange = findMinimizeChange(info, taskIdToMinimize) ?: return false
@@ -76,7 +76,12 @@ class DesktopWindowLimitRemoteHandler(
// have access to RootTaskDisplayAreaOrganizer.
applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
return oneShotRemoteHandler.startAnimation(
- transition, info, startTransaction, finishTransaction, finishCallback)
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
}
private fun applyMinimizeChangeReparenting(
@@ -87,14 +92,15 @@ class DesktopWindowLimitRemoteHandler(
val taskInfo = minimizeChange.taskInfo ?: return
if (taskInfo.isFreeform && TransitionUtil.isOpeningMode(info.type)) {
rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
- taskInfo.displayId, minimizeChange.leash, startTransaction)
+ taskInfo.displayId,
+ minimizeChange.leash,
+ startTransaction,
+ )
}
}
- private fun findMinimizeChange(
- info: TransitionInfo,
- taskIdToMinimize: Int,
- ): Change? =
+ private fun findMinimizeChange(info: TransitionInfo, taskIdToMinimize: Int): Change? =
info.changes.find { change ->
- change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK }
+ change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK
+ }
}
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 9e646f430c98..a6998e1179fa 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
@@ -40,9 +40,7 @@ import kotlinx.coroutines.flow.first
*
* The main constructor is public only for testing purposes.
*/
-class DesktopPersistentRepository(
- private val dataStore: DataStore<DesktopPersistentRepositories>,
-) {
+class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersistentRepositories>) {
constructor(
context: Context,
@ShellBackgroundThread bgCoroutineScope: CoroutineScope,
@@ -51,7 +49,7 @@ class DesktopPersistentRepository(
serializer = DesktopPersistentRepositoriesSerializer,
produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
scope = bgCoroutineScope,
- ),
+ )
)
/** Provides `dataStore.data` flow and handles exceptions thrown during collection */
@@ -63,7 +61,8 @@ class DesktopPersistentRepository(
TAG,
"Error in reading desktop mode related data from datastore, data is " +
"stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
- exception)
+ exception,
+ )
} else {
throw exception
}
@@ -73,13 +72,17 @@ class DesktopPersistentRepository(
* Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If
* the DataStore is empty or there's an error reading, it returns the default value of Proto.
*/
- private suspend fun getDesktopRepositoryState(
- userId: Int = DEFAULT_USER_ID
- ): DesktopRepositoryState? =
+ suspend fun getDesktopRepositoryState(userId: Int): DesktopRepositoryState? =
try {
- dataStoreFlow
- .first()
- .desktopRepoByUserMap[userId]
+ dataStoreFlow.first().desktopRepoByUserMap[userId]
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to read from datastore", e)
+ null
+ }
+
+ suspend fun getUserDesktopRepositoryMap(): Map<Int, DesktopRepositoryState>? =
+ try {
+ dataStoreFlow.first().desktopRepoByUserMap
} catch (e: Exception) {
Log.e(TAG, "Unable to read from datastore", e)
null
@@ -89,10 +92,7 @@ class DesktopPersistentRepository(
* Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the
* [callback] using the [mainCoroutineScope].
*/
- suspend fun readDesktop(
- userId: Int = DEFAULT_USER_ID,
- desktopId: Int = DEFAULT_DESKTOP_ID,
- ): Desktop? =
+ suspend fun readDesktop(userId: Int, desktopId: Int = DEFAULT_DESKTOP_ID): Desktop? =
try {
val repository = getDesktopRepositoryState(userId)
repository?.getDesktopOrThrow(desktopId)
@@ -103,7 +103,7 @@ class DesktopPersistentRepository(
/** Adds or updates a desktop stored in the datastore */
suspend fun addOrUpdateDesktop(
- userId: Int = DEFAULT_USER_ID,
+ userId: Int,
desktopId: Int = 0,
visibleTasks: ArraySet<Int> = ArraySet(),
minimizedTasks: ArraySet<Int> = ArraySet(),
@@ -111,36 +111,33 @@ class DesktopPersistentRepository(
) {
// TODO: b/367609270 - Improve the API to support multi-user
try {
- dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories ->
+ dataStore.updateData { persistentRepositories: DesktopPersistentRepositories ->
val currentRepository =
- desktopPersistentRepositories.getDesktopRepoByUserOrDefault(
- userId, DesktopRepositoryState.getDefaultInstance())
+ persistentRepositories.getDesktopRepoByUserOrDefault(
+ userId,
+ DesktopRepositoryState.getDefaultInstance(),
+ )
val desktop =
getDesktop(currentRepository, desktopId)
.toBuilder()
- .updateTaskStates(
- visibleTasks,
- minimizedTasks,
- freeformTasksInZOrder,
- )
+ .updateTaskStates(visibleTasks, minimizedTasks, freeformTasksInZOrder)
.updateZOrder(freeformTasksInZOrder)
- desktopPersistentRepositories
+ persistentRepositories
.toBuilder()
.putDesktopRepoByUser(
userId,
- currentRepository
- .toBuilder()
- .putDesktop(desktopId, desktop.build())
- .build())
+ currentRepository.toBuilder().putDesktop(desktopId, desktop.build()).build(),
+ )
.build()
}
- } catch (exception: IOException) {
+ } catch (exception: Exception) {
Log.e(
TAG,
"Error in updating desktop mode related data, data is " +
"stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
- exception)
+ exception,
+ )
}
}
@@ -148,13 +145,13 @@ class DesktopPersistentRepository(
// If there are no desktops set up, create one on the default display
currentRepository.getDesktopOrDefault(
desktopId,
- Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build())
+ Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build(),
+ )
companion object {
private const val TAG = "DesktopPersistenceRepo"
private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb"
- private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> {
@@ -185,19 +182,22 @@ class DesktopPersistentRepository(
// visible, they will be marked as not visible afterwards. This ensures that they are
// still persisted as visible.
// TODO - b/350476823: Remove this logic once repository holds expanded tasks
- if (freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
- visibleTasks.isEmpty()
+ if (
+ freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
+ visibleTasks.isEmpty()
) {
visibleTasks.addAll(freeformTasksInZOrder.filterNot { it in minimizedTasks })
}
putAllTasksByTaskId(
visibleTasks.associateWith {
createDesktopTask(it, state = DesktopTaskState.VISIBLE)
- })
+ }
+ )
putAllTasksByTaskId(
minimizedTasks.associateWith {
createDesktopTask(it, state = DesktopTaskState.MINIMIZED)
- })
+ }
+ )
return this
}
@@ -211,7 +211,7 @@ class DesktopPersistentRepository(
private fun createDesktopTask(
taskId: Int,
- state: DesktopTaskState = DesktopTaskState.VISIBLE
+ state: DesktopTaskState = DesktopTaskState.VISIBLE,
): DesktopTask =
DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
}
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 771c3d1cb9a1..a26ebbf4c99a 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
@@ -16,9 +16,9 @@
package com.android.wm.shell.desktopmode.persistence
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
-/** Interface for initializing the [DesktopRepository]. */
+/** Interface for initializing the [DesktopUserRepositories]. */
fun interface DesktopRepositoryInitializer {
- fun initialize(repository: DesktopRepository)
+ fun initialize(userRepositories: DesktopUserRepositories)
}
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 d8156561ff19..58a49a035bb6 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
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode.persistence
import android.content.Context
import android.window.DesktopModeFlags
import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import kotlinx.coroutines.CoroutineScope
@@ -35,32 +36,50 @@ class DesktopRepositoryInitializerImpl(
private val persistentRepository: DesktopPersistentRepository,
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
) : DesktopRepositoryInitializer {
- override fun initialize(repository: DesktopRepository) {
+ override fun initialize(userRepositories: DesktopUserRepositories) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
// TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
mainCoroutineScope.launch {
- val desktop = persistentRepository.readDesktop() ?: return@launch
-
- val maxTasks =
- DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
- ?: desktop.zOrderedTasksCount
-
- var visibleTasksCount = 0
- desktop.zOrderedTasksList
- // Reverse it so we initialize the repo from bottom to top.
- .reversed()
- .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
- .forEach { task ->
- if (task.desktopTaskState == DesktopTaskState.VISIBLE
- && visibleTasksCount < maxTasks
- ) {
- visibleTasksCount++
- repository.addTask(desktop.displayId, task.taskId, isVisible = false)
- } else {
- repository.addTask(desktop.displayId, task.taskId, isVisible = false)
- repository.minimizeTask(desktop.displayId, task.taskId)
- }
+ 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
+ persistentDesktop.zOrderedTasksList
+ // Reverse it so we initialize the repo from bottom to top.
+ .reversed()
+ .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
+ .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,
+ isVisible = false,
+ )
+ repository.minimizeTask(persistentDesktop.displayId, task.taskId)
+ }
+ }
}
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index 9d015357b60b..837a6dd32ff2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -36,7 +36,8 @@ the product.
thread)
- This is always another thread even if config_enableShellMainThread is not set true
- **Note**:
- - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority
+ - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority but can be requested to be boosted
+ to `THREAD_PRIORITY_FOREGROUND`
- `ShellAnimationThread` (currently only used for Transitions and Splitscreen, but potentially all
animations could be offloaded here)
- `ShellSplashScreenThread` (only for use with splashscreens)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 22e8dc186e9b..491b577386d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -31,8 +31,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
-
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
@@ -167,7 +165,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mMainExecutor.executeDelayed(() -> {
mDisplayController.addDisplayWindowListener(this);
}, 0);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
+ mShellController.addExternalInterface(IDragAndDrop.DESCRIPTOR,
this::createExternalInterface, this);
mShellTaskOrganizer.addTaskVanishedListener(this);
mShellCommandHandler.addDumpCallback(this::dump, this);
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 cd20d97c7964..4b59efbcecf2 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
@@ -31,6 +31,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
@@ -49,7 +50,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<DesktopRepository> mDesktopRepository;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
private final Optional<DesktopTasksController> mDesktopTasksController;
private final WindowDecorViewModel mWindowDecorationViewModel;
private final LaunchAdjacentController mLaunchAdjacentController;
@@ -61,7 +62,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorationViewModel,
@@ -69,7 +70,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
mDesktopTasksController = desktopTasksController;
mLaunchAdjacentController = launchAdjacentController;
mTaskChangeListener = taskChangeListener;
@@ -99,8 +100,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopRepository.ifPresent(repository -> {
- repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
+ mDesktopUserRepositories.ifPresent(userRepositories -> {
+ DesktopRepository currentRepo = userRepositories.getProfile(taskInfo.userId);
+ currentRepo.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
});
}
updateLaunchAdjacentController();
@@ -113,21 +115,20 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
mTasks.remove(taskInfo.taskId);
if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
- DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopRepository.ifPresent(repository -> {
- // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
- || repository.isClosingTask(taskInfo.taskId)) {
- // A task that's vanishing should be removed:
- // - If it's closed by the X button which means it's marked as a closing task.
- repository.removeClosingTask(taskInfo.taskId);
- repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
- } else {
- repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */
- false);
- repository.minimizeTask(taskInfo.displayId, taskInfo.taskId);
- }
- });
+ DesktopModeStatus.canEnterDesktopMode(mContext)
+ && mDesktopUserRepositories.isPresent()) {
+ DesktopRepository repository =
+ mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+ // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
+ || !repository.isMinimizedTask(taskInfo.taskId)) {
+ // A task that's vanishing should be removed:
+ // - If it's not yet minimized. It can be minimized when a back navigation is
+ // triggered on a task and the task is closing. It will be marked as minimized in
+ // [DesktopTasksTransitionObserver] before it gets here.
+ repository.removeClosingTask(taskInfo.taskId);
+ repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+ }
}
mWindowDecorationViewModel.onTaskVanished(taskInfo);
updateLaunchAdjacentController();
@@ -148,11 +149,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
// does not propagate all task info changes.
mTaskChangeListener.ifPresent(listener ->
listener.onNonTransitionTaskChanging(taskInfo));
- } else {
- mDesktopRepository.ifPresent(repository -> {
- repository.updateTask(taskInfo.displayId, taskInfo.taskId,
- taskInfo.isVisible);
- });
+ } else if (mDesktopUserRepositories.isPresent()) {
+ DesktopRepository currentRepo =
+ mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+ currentRepo.updateTask(taskInfo.displayId, taskInfo.taskId,
+ taskInfo.isVisible);
}
}
updateLaunchAdjacentController();
@@ -176,10 +177,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
- if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
- mDesktopRepository.ifPresent(repository -> {
- repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
- });
+ if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused
+ && mDesktopUserRepositories.isPresent()) {
+ DesktopRepository repository =
+ mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+ repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 2ae9828ca0db..52b6c62b0721 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_PIP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -99,6 +100,12 @@ public class FreeformTaskTransitionHandler
return token;
}
+ @Override
+ public IBinder startPipTransition(WindowContainerTransaction wct) {
+ final IBinder token = mTransitions.startTransition(TRANSIT_PIP, wct, null);
+ mPendingTransitionTokens.add(token);
+ return token;
+ }
@Override
public IBinder startRemoveTransition(WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index 5984d486f838..a874a5be426d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -51,4 +51,13 @@ public interface FreeformTaskTransitionStarter {
* @return the started transition
*/
IBinder startRemoveTransition(WindowContainerTransaction wct);
+
+ /**
+ * Starts PiP transition
+ *
+ * @param wct the {@link WindowContainerTransaction} that launches the PiP
+ *
+ * @return the started transition
+ */
+ IBinder startPipTransition(WindowContainerTransaction wct);
} \ No newline at end of file
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 4c316de98744..f8e6285b0493 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
@@ -42,7 +42,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
-import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
@@ -444,10 +443,8 @@ public class KeyguardTransitionHandler
@Override
public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- for (Display display : mDisplayController.getDisplays()) {
- wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId())
- .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build());
- }
+ wct.addKeyguardState(new KeyguardState.Builder().setKeyguardShowing(keyguardShowing)
+ .setAodShowing(aodShowing).build());
mMainExecutor.execute(() -> {
mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
wct, KeyguardTransitionHandler.this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 15472ebc149b..862520208d23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -23,7 +23,6 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -298,7 +297,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ mShellController.addExternalInterface(IOneHanded.DESCRIPTOR,
this::createExternalInterface, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
index f7977f88006e..c655d86c3ece 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
@@ -45,9 +45,15 @@ class PerfHintController(private val mContext: Context,
private fun onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this)
val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java)
- val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()),
- TimeUnit.SECONDS.toNanos(1))
- hinter.setAdpfSession(adpfSession)
+ if (perfHintMgr != null) {
+ val adpfSession = perfHintMgr.createHintSession(
+ intArrayOf(Process.myTid()),
+ TimeUnit.SECONDS.toNanos(1)
+ )
+ if (adpfSession != null) {
+ hinter.setAdpfSession(adpfSession)
+ }
+ }
}
fun dump(pw: PrintWriter, prefix: String?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 5276d9d6a4df..55e90e74a404 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -324,6 +324,8 @@ public class PipAnimationController {
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
+ private final Point mLeashOffset = new Point();
+
private T mBaseValue;
protected T mCurrentValue;
protected T mStartValue;
@@ -338,13 +340,22 @@ public class PipAnimationController {
// Flag to avoid double-end
private boolean mHasRequestedEnd;
- private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
- @AnimationType int animationType,
- Rect destinationBounds, T baseValue, T startValue, T endValue) {
+ private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
+ @AnimationType int animationType, @NonNull Rect destinationBounds,
+ @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) {
+ this(taskInfo, leash, animationType, destinationBounds, new Point(), baseValue,
+ startValue, endValue);
+ }
+
+ private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
+ @AnimationType int animationType, @NonNull Rect destinationBounds,
+ @NonNull Point leashOffset, @NonNull T baseValue, @NonNull T startValue,
+ @NonNull T endValue) {
mTaskInfo = taskInfo;
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
+ mLeashOffset.set(leashOffset);
mBaseValue = baseValue;
mStartValue = startValue;
mEndValue = endValue;
@@ -496,6 +507,15 @@ public class PipAnimationController {
}
}
+ /**
+ * Returns the offset of the {@link #mLeash}.
+ */
+ @NonNull
+ Point getLeashOffset() {
+ // Use copy to prevent the leash to be modified unexpectedly.
+ return new Point(mLeashOffset);
+ }
+
void setCurrentValue(T value) {
mCurrentValue = value;
}
@@ -692,8 +712,8 @@ public class PipAnimationController {
final Rect zeroInsets = new Rect(0, 0, 0, 0);
// construct new Rect instances in case they are recycled
- return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
- endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
+ return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, endBounds,
+ leashOffset, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@@ -720,6 +740,7 @@ public class PipAnimationController {
// Use the bounds relative to the task leash in case the leash does not
// start from (0, 0).
final Rect relativeEndBounds = new Rect(end);
+ final Point leashOffset = getLeashOffset();
relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
getSurfaceTransactionHelper()
.crop(tx, leash, relativeEndBounds)
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 fb4afe41e193..af187682d379 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
@@ -94,6 +94,7 @@ import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -152,7 +153,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@Nullable private final PipPerfHintController mPipPerfHintController;
- private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final DisplayController mDisplayController;
protected final ShellTaskOrganizer mTaskOrganizer;
@@ -398,7 +399,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
- Optional<DesktopRepository> desktopRepositoryOptional,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@@ -426,7 +427,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- mDesktopRepositoryOptional = desktopRepositoryOptional;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mDisplayController = displayController;
mTaskOrganizer = shellTaskOrganizer;
@@ -764,7 +765,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// previous freeform bounds that is saved in DesktopRepository.
// 2) If PiP was entered through other means (e.g. user swipe up), exit to initial
// freeform bounds. Note that this case has a flicker at the moment (b/379984108).
- Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+ Rect freeformBounds = getCurrentRepo().removeBoundsBeforeMinimize(
mTaskInfo.taskId);
return freeformBounds != null
? freeformBounds
@@ -779,11 +780,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** Returns whether PiP is exiting while we're in desktop mode. */
// TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
private boolean isPipExitingToDesktopMode() {
- return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
- && (mDesktopRepositoryOptional.get().getVisibleTaskCount(mTaskInfo.displayId) > 0
+ DesktopRepository currentRepo = getCurrentRepo();
+ return Flags.enableDesktopWindowingPip() && currentRepo != null
+ && (currentRepo.getVisibleTaskCount(mTaskInfo.displayId) > 0
|| isDisplayInFreeform());
}
+ private DesktopRepository getCurrentRepo() {
+ return mDesktopUserRepositoriesOptional.map(DesktopUserRepositories::getCurrent).orElse(
+ null);
+ }
+
private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
mTaskOrganizer.applyTransaction(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 72c1ef06806a..0042ec954f32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -507,8 +507,8 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
+ public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
+ @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
@NonNull SurfaceControl.Transaction tx) {
final boolean enteringPip = isInPipDirection(direction);
if (enteringPip) {
@@ -531,12 +531,16 @@ public class PipTransition extends PipTransitionController {
if (mFixedRotationState != FIXED_ROTATION_TRANSITION
&& mFinishTransaction != null) {
mFinishTransaction.merge(tx);
- // Set window crop and position to destination bounds to avoid flickering.
+ // Set crop and position to destination bounds to avoid flickering.
if (hasValidLeash) {
- mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
- destinationBounds.height());
- mFinishTransaction.setPosition(leash, destinationBounds.left,
- destinationBounds.top);
+ final Rect relativeDestinationBounds = new Rect(destinationBounds);
+ relativeDestinationBounds.offset(-leashOffset.x, -leashOffset.y);
+ mFinishTransaction
+ .setCrop(leash, relativeDestinationBounds)
+ // Note that we should set the position to the start position of
+ // leash then the visible region will be at the same place even if
+ // the crop region doesn't start at (0, 0).
+ .setPosition(leash, leashOffset.x, leashOffset.y);
}
}
} else {
@@ -1267,7 +1271,8 @@ public class PipTransition extends PipTransitionController {
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
+ onFinishResize(pipTaskInfo, destinationBounds, animator.getLeashOffset(),
+ TRANSITION_DIRECTION_TO_PIP, tx);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index a273822759f6..6e7740dc13e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -31,6 +31,7 @@ import android.app.PictureInPictureUiState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
@@ -38,6 +39,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -92,7 +94,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
null /* callback */, true /* withStartDelay*/);
}
- onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
+ onFinishResize(taskInfo, animator.getDestinationBounds(),
+ animator.getLeashOffset(), direction, tx);
sendOnPipTransitionFinished(direction);
}
@@ -112,9 +115,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH
* Called when transition is about to finish. This is usually for performing tasks such as
* applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
*/
- public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx) {
+ public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
+ @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
+ @NonNull SurfaceControl.Transaction tx) {
}
/**
@@ -310,6 +313,23 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
+ /**
+ * @return a change representing a config-at-end activity for a given parent.
+ */
+ @Nullable
+ public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
+ @android.annotation.NonNull WindowContainerToken parent) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() == null
+ && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)
+ && change.getParent() != null && change.getParent().equals(parent)) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+
/** Whether a particular package is same as current pip package. */
public boolean isPackageActiveInPip(@Nullable String packageName) {
// No-op, to be handled differently in PIP1 and PIP2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 588b88753eb9..582df486b2b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -32,7 +32,6 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -727,7 +726,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ mShellController.addExternalInterface(IPip.DESCRIPTOR,
this::createExternalInterface, this);
}
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 2c5d346224a3..e309da10864d 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
@@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
-
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
@@ -101,7 +99,7 @@ public class PipController implements ConfigurationChangeListener,
private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
- private PipAnimationListener mPipRecentsAnimationListener;
+ @Nullable private PipAnimationListener mPipRecentsAnimationListener;
@VisibleForTesting
interface PipAnimationListener {
@@ -213,7 +211,7 @@ public class PipController implements ConfigurationChangeListener,
});
// Allow other outside processes to bind to PiP controller using the key below.
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ mShellController.addExternalInterface(IPip.DESCRIPTOR,
this::createExternalInterface, this);
mShellController.addConfigurationChangeListener(this);
@@ -380,7 +378,9 @@ public class PipController implements ConfigurationChangeListener,
tx.setLayer(overlay, Integer.MAX_VALUE);
tx.apply();
}
- mPipRecentsAnimationListener.onPipAnimationStarted();
+ if (mPipRecentsAnimationListener != null) {
+ mPipRecentsAnimationListener.onPipAnimationStarted();
+ }
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
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 5438a014af00..4461a5c6a70c 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -25,6 +26,7 @@ import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -33,13 +35,19 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import java.util.Objects;
+import java.util.Optional;
+
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -50,6 +58,8 @@ public class PipScheduler {
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -61,11 +71,15 @@ public class PipScheduler {
public PipScheduler(Context context,
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
- PipTransitionState pipTransitionState) {
+ PipTransitionState pipTransitionState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -87,7 +101,7 @@ public class PipScheduler {
wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
return wct;
}
@@ -241,6 +255,47 @@ public class PipScheduler {
maybeUpdateMovementBounds();
}
+ /** Returns whether the display is in freeform windowing mode. */
+ private boolean isDisplayInFreeform() {
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
+ if (tdaInfo != null) {
+ return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+ return false;
+ }
+
+ /** Returns whether PiP is exiting while we're in desktop mode. */
+ private boolean isPipExitingToDesktopMode() {
+ return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent()
+ && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
+ Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0
+ || isDisplayInFreeform());
+ }
+
+ /**
+ * 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.
+ */
+ private int getOutPipWindowingMode() {
+ // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+ // freeform windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+ if (isPipExitingToDesktopMode()) {
+ if (isDisplayInFreeform()) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
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 44cc563eadf4..fc3fbe299605 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
@@ -222,7 +222,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
menuController, mainExecutor,
mPipPerfHintController);
- mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
+ mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
+ updateMinMaxSize(aspectRatio);
+ onAspectRatioChanged();
+ });
mMoveOnShelVisibilityChanged = () -> {
if (mIsImeShowing && mImeHeight > mShelfHeight) {
@@ -768,16 +771,17 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
private void animateToNormalSize(Runnable callback) {
// Save the current bounds as the user-resize bounds.
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ final Rect adjustedNormalBounds = getAdjustedNormalBounds();
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
+ getMovementBounds(mPipBoundsState.getBounds()),
+ getMovementBounds(adjustedNormalBounds), callback /* callback */);
+ }
+ private Rect getAdjustedNormalBounds() {
final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio());
final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
- final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(
- normalBounds, minMenuSize);
-
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds,
- getMovementBounds(mPipBoundsState.getBounds()),
- getMovementBounds(adjustedNormalBounds), callback /* callback */);
+ return mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
}
private void animateToUnexpandedState(Rect restoreBounds) {
@@ -1065,6 +1069,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
+
+ if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+ mPipResizeGestureHandler.setUserResizeBounds(getAdjustedNormalBounds());
+ }
}
private Rect getMovementBounds(Rect curBounds) {
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 8f02c1b157b5..dae3c21b6697 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
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -30,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -44,6 +46,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
@@ -52,6 +55,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -193,7 +197,7 @@ public class PipTransition extends PipTransitionController implements
@NonNull TransitionRequestInfo request) {
if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
mEnterTransition = transition;
- return getEnterPipTransaction(transition, request);
+ return getEnterPipTransaction(transition, request.getPipChange());
}
return null;
}
@@ -202,7 +206,8 @@ public class PipTransition extends PipTransitionController implements
public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
@NonNull WindowContainerTransaction outWct) {
if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
- outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
+ outWct.merge(getEnterPipTransaction(transition, request.getPipChange()),
+ true /* transfer */);
mEnterTransition = transition;
}
}
@@ -282,6 +287,31 @@ public class PipTransition extends PipTransitionController implements
}
@Override
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also
+ // be allowed to animate if the task in question is pinned already - see b/308054074.
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN
+ || transitType == TRANSIT_TO_FRONT) {
+ return true;
+ }
+ // This can happen if the request to enter PIP happens when we are collecting for
+ // another transition, such as TRANSIT_CHANGE (display rotation).
+ if (transitType == TRANSIT_CHANGE) {
+ return true;
+ }
+
+ // Please file a bug to handle the unexpected transition type.
+ android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
+ + transitTypeToString(transitType), new Throwable());
+ }
+ return false;
+ }
+
+
+ @Override
public void end() {
if (mTransitionAnimator != null && mTransitionAnimator.isRunning()) {
mTransitionAnimator.end();
@@ -627,6 +657,12 @@ public class PipTransition extends PipTransitionController implements
finishTransition();
});
cacheAndStartTransitionAnimator(animator);
+
+ // Save the PiP bounds in case, we re-enter the PiP with the same component.
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mPipBoundsState.getBounds());
+ mPipBoundsState.saveReentryState(snapFraction);
+
return true;
}
@@ -656,19 +692,6 @@ public class PipTransition extends PipTransitionController implements
}
@Nullable
- private TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
- @NonNull WindowContainerToken parent) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() == null
- && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)
- && change.getParent() != null && change.getParent().equals(parent)) {
- return change;
- }
- }
- return null;
- }
-
- @Nullable
private TransitionInfo.Change getChangeByToken(TransitionInfo info,
WindowContainerToken token) {
for (TransitionInfo.Change change : info.getChanges()) {
@@ -707,6 +730,10 @@ public class PipTransition extends PipTransitionController implements
&& getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
}
+ if (Flags.enableDesktopWindowingPip()) {
+ adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left,
+ -pipActivityChange.getStartAbsBounds().top);
+ }
} else {
// For non-valid app provided src-rect-hint, calculate one to crop into during
// app icon overlay animation.
@@ -754,9 +781,9 @@ public class PipTransition extends PipTransitionController implements
}
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
+ @NonNull TransitionRequestInfo.PipChange pipChange) {
// cache the original task token to check for multi-activity case later
- final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+ final ActivityManager.RunningTaskInfo pipTask = pipChange.getTaskInfo();
PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
mPipTaskListener.setPictureInPictureParams(pipParams);
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
@@ -766,14 +793,18 @@ public class PipTransition extends PipTransitionController implements
final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
mPipBoundsState.setBounds(entryBounds);
+ // Operate on the TF token in case we are dealing with AE case; this should avoid marking
+ // activities in other TFs as config-at-end.
+ WindowContainerToken token = pipChange.getTaskFragmentToken();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
- wct.deferConfigToTransitionEnd(pipTask.token);
+ wct.movePipActivityToPinnedRootTask(token, entryBounds);
+ wct.deferConfigToTransitionEnd(token);
return wct;
}
private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
- final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+ final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
+ ? requestInfo.getPipChange().getTaskInfo() : null;
if (pipTask == null) {
return false;
}
@@ -912,11 +943,6 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- // Save the PiP bounds in case, we re-enter the PiP with the same component.
- float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
- mPipBoundsState.getBounds());
- mPipBoundsState.saveReentryState(snapFraction);
-
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 363c95fcf010..0869caa55369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,7 +24,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
import static com.android.wm.shell.desktopmode.DesktopWallpaperActivity.isWallpaperTask;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.Manifest;
import android.annotation.RequiresPermission;
@@ -63,6 +62,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -72,6 +72,7 @@ import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -89,13 +90,14 @@ import java.util.function.Consumer;
*/
public class RecentTasksController implements TaskStackListenerCallback,
RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
- TaskStackTransitionObserver.TaskStackTransitionObserverListener {
+ TaskStackTransitionObserver.TaskStackTransitionObserverListener, UserChangeListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
- private final Optional<DesktopRepository> mDesktopRepository;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
+
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -108,6 +110,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
// Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
// pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
private final SparseIntArray mSplitTasks = new SparseIntArray();
+
+ private int mUserId;
/**
* Maps taskId to {@link SplitBounds} for both taskIDs.
* Meaning there will be two taskId integers mapping to the same object.
@@ -133,7 +137,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
TaskStackTransitionObserver taskStackTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -141,7 +145,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
return null;
}
return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopRepository,
+ taskStackListener, activityTaskManager, desktopUserRepositories,
taskStackTransitionObserver, mainExecutor);
}
@@ -151,7 +155,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
- Optional<DesktopRepository> desktopRepository,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
TaskStackTransitionObserver taskStackTransitionObserver,
ShellExecutor mainExecutor) {
mContext = context;
@@ -160,7 +164,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mActivityTaskManager = activityTaskManager;
mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
@@ -175,12 +179,15 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
- private void onInit() {
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ void onInit() {
+ mShellController.addExternalInterface(IRecentTasks.DESCRIPTOR,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mUserId = ActivityManager.getCurrentUser();
+ mDesktopUserRepositories.ifPresent(
+ desktopUserRepositories ->
+ desktopUserRepositories.getCurrent().addActiveTaskListener(this));
mTaskStackListener.addListener(this);
- mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
mMainExecutor);
mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
@@ -291,9 +298,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
*/
@Override
public void onRecentTaskRemovedForAddTask(int taskId) {
- mDesktopRepository.ifPresent(
- repo -> repo.removeFreeformTask(INVALID_DISPLAY, taskId)
- );
+ mDesktopUserRepositories.ifPresent(
+ desktopUserRepositories -> desktopUserRepositories.getCurrent().removeFreeformTask(
+ INVALID_DISPLAY, taskId));
}
public void onTaskAdded(RunningTaskInfo taskInfo) {
@@ -512,10 +519,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
// If it's not in the mapping, then it was already paired with another task
continue;
}
-
- if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && mDesktopRepository.isPresent()
- && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
+ if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
+ mDesktopUserRepositories.isPresent()
+ && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
mostRecentFreeformTaskIndex = groupedTasks.size();
@@ -531,7 +537,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
taskInfo.lastNonFullscreenBounds.top);
}
freeformTasks.add(taskInfo);
- if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
+ if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
minimizedFreeformTasks.add(taskInfo.taskId);
}
continue;
@@ -544,7 +550,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
mTaskSplitBoundsMap.get(pairedTaskId)));
} else {
- if (Flags.enableRefactorTaskThumbnail() && isWallpaperTask(taskInfo)) {
+ if (
+ Flags.enableUseTopVisibleActivityForExcludeFromRecentTask()
+ && isWallpaperTask(taskInfo)) {
// Don't add the wallpaper task as an entry in grouped tasks
continue;
}
@@ -703,6 +711,21 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
}
+ @Override
+ public void onUserChanged(int newUserId, @NonNull Context userContext) {
+ if (mDesktopUserRepositories.isEmpty()) return;
+
+ DesktopRepository previousUserRepository =
+ mDesktopUserRepositories.get().getProfile(mUserId);
+ mUserId = newUserId;
+ DesktopRepository currentUserRepository =
+ mDesktopUserRepositories.get().getProfile(newUserId);
+
+ // No-op if both profile ids map to the same user.
+ if (previousUserRepository.getUserId() == currentUserRepository.getUserId()) return;
+ previousUserRepository.removeActiveTasksListener(this);
+ currentUserRepository.addActiveTaskListener(this);
+ }
/**
* The interface for calls from outside the host process.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6e0e696e15fe..a368245db25f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -32,7 +32,6 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
@@ -94,6 +93,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.SplitDragPolicy;
@@ -199,6 +199,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
private final MultiInstanceHelper mMultiInstanceHelpher;
+ private final SplitState mSplitState;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
@VisibleForTesting
@@ -228,6 +229,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
Optional<DesktopTasksController> desktopTasksController,
@Nullable StageCoordinator stageCoordinator,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
ShellExecutor mainExecutor,
Handler mainHandler) {
mShellCommandHandler = shellCommandHandler;
@@ -252,6 +254,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
mDesktopTasksController = desktopTasksController;
mStageCoordinator = stageCoordinator;
mMultiInstanceHelpher = multiInstanceHelper;
+ mSplitState = splitState;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -278,7 +281,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ mShellController.addExternalInterface(ISplitScreen.DESCRIPTOR,
this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -296,7 +299,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
- mWindowDecorViewModel);
+ mWindowDecorViewModel, mSplitState);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 88a95660f098..b40996f52bd3 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
@@ -139,6 +139,7 @@ import com.android.wm.shell.common.split.OffscreenTouchZone;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
@@ -218,6 +219,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Optional<RecentTasksController> mRecentTasks;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ /** Singleton source of truth for the current state of split screen on this device. */
+ private final SplitState mSplitState;
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
@@ -344,7 +347,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -355,6 +358,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mSplitState = splitState;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
@@ -412,7 +416,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
Handler mainHandler, Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -432,6 +436,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mSplitState = splitState;
+
mDisplayController.addDisplayWindowListener(this);
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -501,6 +507,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* Deactivates main stage by removing the stage from the top level split root (usually when a
* task underneath gets removed from the stage root).
+ * This function should always be called as part of exiting split screen.
* @param stageToTop stage which we want to put on top
*/
private void deactivateSplit(WindowContainerTransaction wct, @StageType int stageToTop) {
@@ -833,7 +840,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, mSideStage);
+ StageTaskListener stageForTask1;
+ if (enableFlexibleSplit()) {
+ stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ stageForTask1 = mSideStage;
+ }
+ addActivityOptions(options1, stageForTask1);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
prepareTasksForSplitScreen(new int[] {taskId}, wct);
@@ -878,7 +892,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, mSideStage);
+ StageTaskListener stageForTask1;
+ if (enableFlexibleSplit()) {
+ stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ stageForTask1 = mSideStage;
+ }
+ addActivityOptions(options1, stageForTask1);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
prepareTasksForSplitScreen(new int[] {taskId}, wct);
@@ -1010,14 +1031,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setRootForceTranslucent(false, wct);
options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, mSideStage);
+ StageTaskListener stageForTask1;
+ if (enableFlexibleSplit()) {
+ stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ stageForTask1 = mSideStage;
+ }
+ addActivityOptions(options1, stageForTask1);
if (shortcutInfo1 != null) {
wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
} else {
wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
}
+
+ StageTaskListener stageForTask2;
+ if (enableFlexibleSplit()) {
+ stageForTask2 = mStageOrderOperator.getStageForLegacyPosition(
+ reverseSplitPosition(splitPosition), true /*checkAllStagesIfNotActive*/);
+ } else {
+ stageForTask2 = mMainStage;
+ }
options2 = options2 != null ? options2 : new Bundle();
- addActivityOptions(options2, mMainStage);
+ addActivityOptions(options2, stageForTask2);
if (shortcutInfo2 != null) {
wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
} else {
@@ -1246,9 +1282,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Restore focus-ability to the windows and divider
wct.setFocusable(mRootTaskInfo.token, true);
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator.onDoubleTappedDivider();
+ }
setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
+ mSplitLayout.updateStateWithCurrentPosition();
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
// updateSurfaceBounds(), above, officially puts the two apps in their new
@@ -1404,6 +1444,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!isSplitActive() || mIsExiting) return;
onSplitScreenExit();
+ mSplitState.exit();
clearSplitPairedInRecents(exitReason);
mShouldUpdateRecents = false;
@@ -1599,6 +1640,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
}
deactivateSplit(wct, stageToTop);
+ mSplitState.exit();
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -1697,6 +1739,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
+ mSplitLayout.updateStateWithCurrentPosition();
mSplitLayout.update(null, true /* resetImePosition */);
if (enableFlexibleSplit()) {
runForActiveStages((stage) ->
@@ -1921,6 +1964,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
+
if (Flags.enableFlexibleTwoAppSplit()) {
// Split screen can be laid out in such a way that some of the apps are offscreen.
// For the purposes of passing SplitBounds up to launcher (for use in thumbnails
@@ -1933,10 +1978,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
bottomRightBounds.top =
Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+
+ // TODO (b/349828130): Can change to getState() fully after brief soak time.
+ if (mSplitState.get() != currentSnapPosition) {
+ Log.wtf(TAG, "SplitState is " + mSplitState.get()
+ + ", expected " + currentSnapPosition);
+ currentSnapPosition = mSplitState.get();
+ }
}
SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
- leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
+ leftTopTaskId, rightBottomTaskId, currentSnapPosition);
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
@@ -1975,7 +2027,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
- PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
+ PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
index b7b3c9b62a58..3fa8df40dfef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
@@ -38,6 +38,7 @@ import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B
import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_C
import com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString
import com.android.wm.shell.windowdecor.WindowDecorViewModel
+import java.util.Collections
import java.util.Optional
/**
@@ -148,6 +149,24 @@ class StageOrderOperator (
}
/**
+ * This will swap the stages for the two stages on either side of the given divider.
+ * Note: This will keep [activeStages] and [allStages] in sync by swapping both of them
+ * If there are no [activeStages] then this will be a no-op.
+ *
+ * TODO(b/379984874): Take in a divider identifier to determine which array indices to swap
+ */
+ fun onDoubleTappedDivider() {
+ if (activeStages.isEmpty()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Stages not active, ignoring swap request")
+ return
+ }
+
+ Collections.swap(activeStages, 0, 1)
+ Collections.swap(allStages, 0, 1)
+ }
+
+ /**
* Returns a legacy split position for the given stage. If no stages are active then this will
* return [SPLIT_POSITION_UNDEFINED]
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 34681569a16c..c5e158c6b452 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -32,6 +32,7 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -60,6 +61,7 @@ public class TvSplitScreenController extends SplitScreenController {
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final LaunchAdjacentController mLaunchAdjacentController;
+ private final SplitState mSplitState;
private final Handler mMainHandler;
private final SystemWindows mSystemWindows;
@@ -80,6 +82,7 @@ public class TvSplitScreenController extends SplitScreenController {
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
MultiInstanceHelper multiInstanceHelper,
+ SplitState splitState,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
@@ -87,8 +90,8 @@ public class TvSplitScreenController extends SplitScreenController {
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
- mainHandler);
+ Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, splitState,
+ mainExecutor, mainHandler);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -102,6 +105,7 @@ public class TvSplitScreenController extends SplitScreenController {
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
+ mSplitState = splitState;
mMainHandler = mainHandler;
mSystemWindows = systemWindows;
@@ -117,7 +121,7 @@ public class TvSplitScreenController extends SplitScreenController {
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool,
mIconProvider, mMainExecutor, mMainHandler,
- mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
+ mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4451ee887363..ef1f88e635d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.split.SplitScreenConstants;
@@ -53,10 +54,12 @@ public class TvStageCoordinator extends StageCoordinator
Handler mainHandler,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ SplitState splitState,
SystemWindows systemWindows) {
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
+ mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty(),
+ splitState);
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 7cb8e8aa7b49..72bad4193f4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,8 +23,6 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
-
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
@@ -119,7 +117,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ mShellController.addExternalInterface(IStartingWindow.DESCRIPTOR,
this::createExternalInterface, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 82c0aaf3bc8b..361d766370e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -186,6 +186,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
*/
public void setObscuredTouchRect(Rect obscuredRect) {
mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null;
+ invalidate();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 92d1f9c26bbc..41c0a11827bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -287,6 +287,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
// Postpone transition splitting to later.
WindowContainerTransaction out = new WindowContainerTransaction();
+ mPipHandler.augmentRequest(transition, request, out);
return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 3d3de88cdafc..03ded730865e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.transition;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
@@ -37,6 +38,8 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+import java.util.List;
+
class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
private final UnfoldTransitionHandler mUnfoldHandler;
private final ActivityEmbeddingController mActivityEmbeddingController;
@@ -127,6 +130,13 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
}
}
+ TransitionInfo.Change pipActivityChange = null;
+ if (pipChange != null) {
+ pipActivityChange = mPipHandler.getDeferConfigActivityChange(
+ info, pipChange.getContainer());
+ everythingElse.getChanges().remove(pipActivityChange);
+ }
+
final Transitions.TransitionFinishCallback finishCB = (wct) -> {
--mInFlightSubAnimations;
joinFinishArgs(wct);
@@ -139,13 +149,23 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
return false;
}
- // PIP window should always be on the highest Z order.
- if (pipChange != null) {
+ if (pipChange != null && pipActivityChange == null) {
+ // We are operating on a single PiP task for the enter animation here.
mInFlightSubAnimations = 2;
+ // PIP window should always be on the highest Z order.
mPipHandler.startEnterAnimation(
pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
finishTransaction,
finishCB);
+ } else if (pipActivityChange != null) {
+ // If there is both a PiP task and a PiP config-at-end activity change,
+ // put them into a separate TransitionInfo, and send to be animated as TRANSIT_PIP.
+ mInFlightSubAnimations = 2;
+ TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */);
+ pipInfo.getChanges().addAll(List.of(pipChange, pipActivityChange));
+ mPipHandler.startAnimation(mTransition, pipInfo,
+ startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction, finishCB);
} else {
mInFlightSubAnimations = 1;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index 4ea4613185e1..d8884f6d8d38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -41,9 +41,9 @@ public class DefaultSurfaceAnimator {
@NonNull Animation anim, @NonNull SurfaceControl leash,
@NonNull Runnable finishCallback, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
- @Nullable Rect clipRect, boolean isActivity) {
+ @Nullable Rect clipRect) {
final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash,
- position, clipRect, cornerRadius, isActivity);
+ position, clipRect, cornerRadius);
buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter);
}
@@ -138,11 +138,9 @@ public class DefaultSurfaceAnimator {
@Nullable final Rect mClipRect;
@Nullable private final Rect mAnimClipRect;
final float mCornerRadius;
- final boolean mIsActivity;
DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash,
- @Nullable Point position, @Nullable Rect clipRect, float cornerRadius,
- boolean isActivity) {
+ @Nullable Point position, @Nullable Rect clipRect, float cornerRadius) {
super(leash);
mAnim = anim;
mPosition = (position != null && (position.x != 0 || position.y != 0))
@@ -150,7 +148,6 @@ public class DefaultSurfaceAnimator {
mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
mAnimClipRect = mClipRect != null ? new Rect() : null;
mCornerRadius = cornerRadius;
- mIsActivity = isActivity;
}
@Override
@@ -160,10 +157,6 @@ public class DefaultSurfaceAnimator {
final SurfaceControl leash = mLeash;
transformation.clear();
mAnim.getTransformation(currentPlayTime, transformation);
- if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
- && mIsActivity && mAnim.getExtensionEdges() != 0) {
- t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges());
- }
if (mPosition != null) {
transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
}
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 9fcf98b9efc2..e80016d07f15 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
@@ -506,6 +506,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (!isTask && a.getExtensionEdges() != 0x0) {
if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ startTransaction.setEdgeExtensionEffect(
+ change.getLeash(), a.getExtensionEdges());
finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
} else {
if (!TransitionUtil.isOpeningType(mode)) {
@@ -564,7 +566,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
mTransactionPool, mMainExecutor, animRelOffset, cornerRadius,
- clipRect, change.getActivityComponent() != null);
+ clipRect);
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
@@ -876,8 +878,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
- change.getActivityComponent() != null);
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@@ -901,8 +902,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
- change.getActivityComponent() != null);
+ mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
}
private static int getWallpaperTransitType(TransitionInfo info) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 6f3aa11a8f52..aa42b7f0ca76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -347,21 +347,21 @@ class ScreenRotationAnimation {
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
- null /* clipRect */, false /* isActivity */);
+ null /* clipRect */);
}
private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
- null /* clipRect */, false /* isActivity */);
+ null /* clipRect */);
}
private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
- null /* clipRect */, false /* isActivity */);
+ null /* clipRect */);
}
private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 3f191497e1ed..611f3e0ac5e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -41,7 +41,6 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -373,7 +372,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mOrganizer.shareTransactionQueue();
}
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ mShellController.addExternalInterface(IShellTransitions.DESCRIPTOR,
this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index c9f2d2e8c0e2..0b919668f7fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -63,10 +63,13 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
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.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -89,6 +92,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
private final Transitions mTransitions;
private final Region mExclusionRegion = Region.obtain();
private final InputManager mInputManager;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private TaskOperations mTaskOperations;
private FocusTransitionObserver mFocusTransitionObserver;
@@ -119,8 +123,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
+ @ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread ShellExecutor bgExecutor,
- ShellExecutor shellExecutor,
Choreographer mainChoreographer,
IWindowManager windowManager,
ShellInit shellInit,
@@ -129,7 +133,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -142,6 +147,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
mSyncQueue = syncQueue;
mTransitions = transitions;
mFocusTransitionObserver = focusTransitionObserver;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -331,7 +337,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
mMainHandler,
mBgExecutor,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mWindowDecorViewHostSupplier);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 982fda0ddf36..23bb2aa616f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -58,6 +58,8 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -90,8 +92,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
Handler handler,
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface);
+ SyncTransactionQueue syncQueue,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, windowDecorViewHostSupplier);
mHandler = handler;
mBgExecutor = bgExecutor;
mChoreographer = choreographer;
@@ -194,6 +198,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@VisibleForTesting
static void updateRelayoutParams(
RelayoutParams relayoutParams,
+ @NonNull Context context,
ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw,
boolean shouldSetTaskVisibilityPositionAndCrop,
@@ -206,9 +211,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
- relayoutParams.mShadowRadiusId = hasGlobalFocus
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ relayoutParams.mShadowRadius = hasGlobalFocus
+ ? context.getResources().getDimensionPixelSize(
+ R.dimen.freeform_decor_shadow_focused_thickness)
+ : context.getResources().getDimensionPixelSize(
+ R.dimen.freeform_decor_shadow_unfocused_thickness);
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
@@ -251,7 +258,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
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 101467df03ac..ff52a45c94e2 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
@@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
@@ -51,7 +51,7 @@ class DesktopHeaderManageWindowsMenu(
private val displayController: DisplayController,
private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
context: Context,
- private val desktopRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>,
private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
snapshotList: List<Pair<Int, TaskSnapshot>>,
@@ -76,6 +76,7 @@ class DesktopHeaderManageWindowsMenu(
val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
&& desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
// Use system view container so that forcibly shown system bars take effect in
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 04ef7c1dc461..0f5813c7807b 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
@@ -79,6 +79,7 @@ import android.view.SurfaceControl;
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;
@@ -111,13 +112,17 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
+import com.android.wm.shell.desktopmode.DesktopModeUtils;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopRepository;
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;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -135,6 +140,8 @@ 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.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -166,7 +173,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
- private final DesktopRepository mDesktopRepository;
+ private final DesktopUserRepositories mDesktopUserRepositories;
private final ShellController mShellController;
private final Context mContext;
private final @ShellMainThread Handler mMainHandler;
@@ -213,6 +220,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mInImmersiveMode;
private final String mSysUIPackageName;
private final AssistContentRequester mAssistContentRequester;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
private final ISystemGestureExclusionListener mGestureExclusionListener =
@@ -244,7 +252,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -256,6 +264,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -275,7 +284,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
shellCommandHandler,
windowManager,
taskOrganizer,
- desktopRepository,
+ desktopUserRepositories,
displayController,
shellController,
displayInsetsController,
@@ -285,6 +294,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
desktopImmersiveController,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
@@ -315,7 +325,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
ShellCommandHandler shellCommandHandler,
IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
DisplayController displayController,
ShellController shellController,
DisplayInsetsController displayInsetsController,
@@ -325,6 +335,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
DesktopImmersiveController desktopImmersiveController,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
@@ -349,7 +360,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mBgExecutor = bgExecutor;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mTaskOrganizer = taskOrganizer;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
@@ -377,6 +388,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
DesktopModeWindowDecoration decoration;
RunningTaskInfo taskInfo;
@@ -576,38 +588,71 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private void openHandleMenu(int taskId) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
- decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo)
- >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
+ // TODO(b/379873022): Run the instance check and the AssistContent request in
+ // createHandleMenu on the same bg thread dispatch.
+ mBgExecutor.execute(() -> {
+ final int numOfInstances = checkNumberOfOtherInstances(decoration.mTaskInfo);
+ mMainExecutor.execute(() -> {
+ decoration.createHandleMenu(
+ numOfInstances >= MANAGE_WINDOWS_MINIMUM_INSTANCES
+ );
+ });
+ });
}
- private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger,
- MotionEvent motionEvent) {
+ private void onToggleSizeInteraction(
+ int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+ @Nullable MotionEvent motionEvent) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
- mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger,
- DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), () -> {
- mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, mMainHandler,
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
- return null;
- }, () -> {
- mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, mMainHandler,
- Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW, source);
- return null;
- });
+ final ToggleTaskSizeInteraction interaction =
+ createToggleSizeInteraction(decoration, source, motionEvent);
+ if (interaction == null) {
+ return;
+ }
+ if (interaction.getCujTracing() != null) {
+ mInteractionJankMonitor.begin(
+ decoration.mTaskSurface, mContext, mMainHandler,
+ interaction.getCujTracing(), interaction.getJankTag());
+ }
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
- private void onEnterOrExitImmersive(int taskId) {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ private ToggleTaskSizeInteraction createToggleSizeInteraction(
+ @NonNull DesktopModeWindowDecoration decoration,
+ @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+ @Nullable MotionEvent motionEvent) {
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId);
+ if (displayLayout == null) {
+ return null;
+ }
+ final Rect stableBounds = new Rect();
+ displayLayout.getStableBounds(stableBounds);
+ boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds);
+
+ return new ToggleTaskSizeInteraction(
+ isMaximized
+ ? ToggleTaskSizeInteraction.Direction.RESTORE
+ : ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeUtilsKt.toSource(source, isMaximized),
+ DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent)
+ );
+ }
+
+ private void onEnterOrExitImmersive(RunningTaskInfo taskInfo) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) {
return;
}
- if (mDesktopRepository.isTaskInFullImmersiveState(taskId)) {
+ final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+ taskInfo.userId);
+ if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE);
mDesktopImmersiveController.moveTaskToNonImmersive(
@@ -726,12 +771,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return;
}
decoration.closeHandleMenu();
- decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo),
- /* onIconClickListener= */(Integer requestedTaskId) -> {
- decoration.closeManageWindowsMenu();
- mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId);
- return Unit.INSTANCE;
- });
+ mBgExecutor.execute(() -> {
+ final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList =
+ getTaskSnapshots(decoration.mTaskInfo);
+ mMainExecutor.execute(() -> decoration.createManageWindowsMenu(
+ snapshotList,
+ /* onIconClickListener= */ (Integer requestedTaskId) -> {
+ decoration.closeManageWindowsMenu();
+ mDesktopTasksController.openInstance(decoration.mTaskInfo,
+ requestedTaskId);
+ return Unit.INSTANCE;
+ }
+ )
+ );
+ });
}
private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots(
@@ -802,7 +855,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private boolean mIsResizeGesture;
private boolean mIsDragging;
private boolean mTouchscreenInUse;
- private boolean mHasLongClicked;
private int mDragPointerId = -1;
private MotionEvent mMotionEvent;
@@ -868,12 +920,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) {
// Task is requesting immersive, so it should either enter or exit immersive,
// depending on immersive state.
- onEnterOrExitImmersive(decoration.mTaskInfo.taskId);
+ onEnterOrExitImmersive(decoration.mTaskInfo);
} else {
// Full immersive is disabled or task doesn't request/support it, so just
// toggle between maximize/restore states.
- onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button",
- ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent);
+ onToggleSizeInteraction(decoration.mTaskInfo.taskId,
+ ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
}
} else if (id == R.id.minimize_window) {
mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
@@ -937,8 +989,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
if (mInputManager != null
&& !Flags.enableAccessibleCustomHeaders()) {
- // Pilfer so that windows below receive cancellations for this gesture.
- mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ ViewRootImpl viewRootImpl = v.getViewRootImpl();
+ if (viewRootImpl != null) {
+ // Pilfer so that windows below receive cancellations for this gesture.
+ mInputManager.pilferPointers(viewRootImpl.getInputToken());
+ }
}
if (isUpOrCancel) {
// Gesture is finished, reset state.
@@ -961,7 +1016,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (decoration.isMaximizeMenuActive()) {
decoration.closeMaximizeMenu();
} else {
- mHasLongClicked = true;
mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
decoration.createMaximizeMenu();
@@ -1000,6 +1054,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
+ mDesktopModeUiEventLogger.log(taskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS);
mDesktopTasksController.moveTaskToFront(taskInfo);
}
}
@@ -1056,8 +1112,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
|| id == R.id.open_menu_button || id == R.id.minimize_window);
+ final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+ taskInfo.userId);
final boolean dragAllowed =
- !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
+ !desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (dragAllowed) {
@@ -1068,7 +1126,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
updateDragStatus(e.getActionMasked());
mOnDragStartInitialBounds.set(initialBounds);
}
- mHasLongClicked = false;
// Do not consume input event if a button is touched, otherwise it would
// prevent the button's ripple effect from showing.
return !touchingButton;
@@ -1102,6 +1159,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (!wasDragging) {
return false;
}
+ mDesktopModeUiEventLogger.log(taskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG);
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
@@ -1123,7 +1182,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
newTaskBounds, decoration.calculateValidDragArea(),
new Rect(mOnDragStartInitialBounds), e,
mWindowDecorByTaskId.get(taskInfo.taskId));
- if (touchingButton && !mHasLongClicked) {
+ if (touchingButton) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
// onClick call that results.
@@ -1165,11 +1224,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
- if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) {
+ final DesktopRepository desktopRepository = mDesktopUserRepositories.getCurrent();
+ if (desktopRepository.isTaskInFullImmersiveState(mTaskId)) {
// Disallow double-tap to resize when in full immersive.
return false;
}
- onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e);
+ onToggleSizeInteraction(mTaskId,
+ ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e);
return true;
}
}
@@ -1573,11 +1634,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
final DesktopModeWindowDecoration windowDecoration =
mDesktopModeWindowDecorFactory.create(
- mContext,
+ Flags.enableBugFixesForSecondaryDisplay()
+ ? mDisplayController.getDisplayContext(taskInfo.displayId)
+ : mContext,
mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
mDisplayController,
mSplitScreenController,
- mDesktopRepository,
+ mDesktopUserRepositories,
mTaskOrganizer,
taskInfo,
taskSurface,
@@ -1589,6 +1652,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
+ mWindowDecorViewHostSupplier,
mMultiInstanceHelper,
mWindowDecorCaptionHandleRepository,
mDesktopModeEventLogger);
@@ -1608,12 +1672,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
- onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
+ onToggleSizeInteraction(taskInfo.taskId,
+ ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU,
touchEventListener.mMotionEvent);
return Unit.INSTANCE;
});
windowDecoration.setOnImmersiveOrRestoreClickListener(() -> {
- onEnterOrExitImmersive(taskInfo.taskId);
+ onEnterOrExitImmersive(taskInfo);
return Unit.INSTANCE;
});
windowDecoration.setOnLeftSnapClickListener(() -> {
@@ -1767,11 +1832,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// TODO(b/336289597): Rather than returning number of instances, return a list of valid
// instances, then refer to the list's size and reuse the list for Manage Windows menu.
final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
- final IActivityManager activityManager = ActivityManager.getService();
try {
return activityTaskManager.getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_WITH_EXCLUDED,
- activityManager.getCurrentUserId()).getList().stream().filter(
+ info.userId).getList().stream().filter(
recentTaskInfo -> (recentTaskInfo.taskId != info.taskId
&& recentTaskInfo.baseActivity != null
&& recentTaskInfo.baseActivity.getPackageName()
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 96cc559a64ae..6562f38e724d 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
@@ -43,7 +43,6 @@ import static com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.Dr
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.app.assist.AssistContent;
@@ -99,13 +98,15 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -128,7 +129,6 @@ import java.util.function.Supplier;
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
- private static final int CAPTURED_LINK_TIMEOUT_MS = 7000;
@VisibleForTesting
static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
@@ -158,14 +158,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Function0<Unit> mOnMaximizeHoverListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
- private Runnable mCurrentViewHostRunnable = null;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private DisabledEdge mDisabledResizingEdge =
NONE;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
- private final Runnable mViewHostRunnable =
- () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult);
private final Point mPositionInParent = new Point();
private HandleMenu mHandleMenu;
@@ -203,17 +200,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// being hovered. There's a small delay after stopping the hover, to allow a quick reentry
// to cancel the close.
private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
- private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
- private final DesktopRepository mDesktopRepository;
+ private final DesktopUserRepositories mDesktopUserRepositories;
public DesktopModeWindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -225,17 +221,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
- this (context, userContext, displayController, splitScreenController, desktopRepository,
- taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
- appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
- assistContentRequester,
+ this (context, userContext, displayController, splitScreenController,
+ desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
+ bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory,
+ rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {},
+ windowDecorViewHostSupplier,
DefaultMaximizeMenuFactory.INSTANCE,
DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
windowDecorCaptionHandleRepository, desktopModeEventLogger);
@@ -246,7 +244,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -264,15 +262,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Supplier<SurfaceControl> surfaceControlSupplier,
WindowManagerWrapper windowManagerWrapper,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MaximizeMenuFactory maximizeMenuFactory,
HandleMenuFactory handleMenuFactory,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
- surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger);
mSplitScreenController = splitScreenController;
mHandler = handler;
mBgExecutor = bgExecutor;
@@ -287,7 +286,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mMultiInstanceHelper = multiInstanceHelper;
mWindowManagerWrapper = windowManagerWrapper;
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
- mDesktopRepository = desktopRepository;
+ mDesktopUserRepositories = desktopUserRepositories;
}
/**
@@ -437,7 +436,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
public void updateDisabledResizingEdge(
DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate) {
mDisabledResizingEdge = disabledResizingEdge;
- final boolean inFullImmersive = mDesktopRepository
+ final boolean inFullImmersive = mDesktopUserRepositories.getCurrent()
.isTaskInFullImmersiveState(mTaskInfo.taskId);
if (shouldDelayUpdate) {
return;
@@ -451,78 +450,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (taskInfo.isFreeform()) {
- // The Task is in Freeform mode -> show its header in sync since it's an integral part
- // of the window itself - a delayed header might cause bad UX.
- relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
- } else {
- // The Task is outside Freeform mode -> allow the handle view to be delayed since the
- // handle is just a small addition to the window.
- relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
- }
- Trace.endSection();
- }
-
- /** Run the whole relayout phase immediately without delay. */
- private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
- // Clear the current ViewHost runnable as we will update the ViewHost here
- clearCurrentViewHostRunnable();
- updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
- if (mResult.mRootView != null) {
- updateViewHost(mRelayoutParams, startT, mResult);
- }
- }
- /**
- * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been
- * updated.
- */
- private void clearCurrentViewHostRunnable() {
- if (mCurrentViewHostRunnable != null) {
- mHandler.removeCallbacks(mCurrentViewHostRunnable);
- mCurrentViewHostRunnable = null;
- }
- }
-
- /**
- * Relayout the window decoration but repost some of the work, to unblock the current callstack.
- */
- private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus,
- @NonNull Region displayExclusionRegion) {
- if (applyStartTransactionOnDraw) {
- throw new IllegalArgumentException(
- "We cannot both sync viewhost ondraw and delay viewhost creation.");
- }
- // Clear the current ViewHost runnable as we will update the ViewHost here
- clearCurrentViewHostRunnable();
- updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus, displayExclusionRegion);
- if (mResult.mRootView == null) {
- // This means something blocks the window decor from showing, e.g. the task is hidden.
- // Nothing is set up in this case including the decoration surface.
- return;
- }
- // Store the current runnable so it can be removed if we start a new relayout.
- mCurrentViewHostRunnable = mViewHostRunnable;
- mHandler.post(mCurrentViewHostRunnable);
- }
-
- @SuppressLint("MissingPermission")
- private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
- Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
if (Flags.enableDesktopWindowingAppToWeb()) {
setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
}
@@ -541,11 +469,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOpenByDefaultDialog.relayout(taskInfo);
}
- final boolean inFullImmersive = mDesktopRepository
+ final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
.isTaskInFullImmersiveState(taskInfo.taskId);
- updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
- mIsKeyguardVisibleAndOccluded, inFullImmersive,
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
+ applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
+ mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
displayExclusionRegion);
@@ -553,9 +481,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces");
- updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
- Trace.endSection();
+ relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT");
@@ -570,7 +496,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeStatusBarInputLayer();
- Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
+ Trace.endSection(); // DesktopModeWindowDecoration#relayout
return;
}
@@ -578,7 +504,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
disposeStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
}
- Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
final Point position = new Point();
if (isAppHandle(mWindowDecorViewHolder)) {
@@ -588,6 +513,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
notifyCaptionStateChanged();
}
+ Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
if (isAppHandle(mWindowDecorViewHolder)) {
mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData(
mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
@@ -612,7 +538,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
updateDragResizeListener(oldDecorationSurface, inFullImmersive);
updateMaximizeMenu(startT, inFullImmersive);
- Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
+ Trace.endSection(); // DesktopModeWindowDecoration#relayout
}
private boolean isCaptionVisible() {
@@ -625,22 +551,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return;
}
mCapturedLink = new CapturedLink(capturedLink, timeStamp);
- mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS);
- }
-
- private void onCapturedLinkExpired() {
- mHandler.removeCallbacks(mCapturedLinkExpiredRunnable);
- if (mCapturedLink != null) {
- mCapturedLink.setExpired();
- }
}
@Nullable
private Intent getBrowserLink() {
final Uri browserLink;
- // If the captured link is available and has not expired, return the captured link.
- // Otherwise, return the generic link which is set to null if a generic link is unavailable.
- if (mCapturedLink != null && !mCapturedLink.mExpired) {
+ if (isCapturedLinkAvailable()) {
browserLink = mCapturedLink.mUri;
} else if (mWebUri != null) {
browserLink = mWebUri;
@@ -752,7 +668,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
private boolean isCapturedLinkAvailable() {
- return mCapturedLink != null && !mCapturedLink.mExpired;
+ return mCapturedLink != null && !mCapturedLink.mUsed;
+ }
+
+ private void onCapturedLinkUsed() {
+ if (mCapturedLink != null) {
+ mCapturedLink.setUsed();
+ }
}
private void notifyNoCaptionHandle() {
@@ -877,6 +799,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
RelayoutParams relayoutParams,
Context context,
ActivityManager.RunningTaskInfo taskInfo,
+ SplitScreenController splitScreenController,
boolean applyStartTransactionOnDraw,
boolean shouldSetTaskVisibilityPositionAndCrop,
boolean isStatusBarVisible,
@@ -896,6 +819,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
relayoutParams.mHasGlobalFocus = hasGlobalFocus;
relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
+ // Allow the handle view to be delayed since the handle is just a small addition to the
+ // window, whereas the header cannot be delayed because it is expected to be visible from
+ // the first frame.
+ relayoutParams.mAsyncViewHost = isAppHandle;
final boolean showCaption;
if (Flags.enableFullyImmersiveInDesktop()) {
@@ -918,7 +845,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
}
relayoutParams.mIsCaptionVisible = showCaption;
- relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode;
+ final boolean isBottomSplit = !splitScreenController.isLeftRightSplit()
+ && splitScreenController.getSplitPosition(taskInfo.taskId)
+ == SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ relayoutParams.mIsInsetSource = (isAppHeader && !inFullImmersiveMode) || isBottomSplit;
if (isAppHeader) {
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// The app is requesting to customize the caption bar, which means input on
@@ -980,10 +910,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mInputFeatures
|= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
}
- if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
- relayoutParams.mShadowRadiusId = hasGlobalFocus
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ if (isAppHeader
+ && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
+ relayoutParams.mShadowRadius = hasGlobalFocus
+ ? context.getResources().getDimensionPixelSize(
+ R.dimen.freeform_decor_shadow_focused_thickness)
+ : context.getResources().getDimensionPixelSize(
+ R.dimen.freeform_decor_shadow_unfocused_thickness);
+ } else {
+ relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS;
}
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
@@ -1297,9 +1232,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
mDisplayController, mTaskInfo, mContext,
calculateMaximizeMenuPosition(menuWidth), mSurfaceControlTransactionSupplier);
+
mMaximizeMenu.show(
/* isTaskInImmersiveMode= */ Flags.enableFullyImmersiveInDesktop()
- && mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId),
+ && mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+ .isTaskInFullImmersiveState(mTaskInfo.taskId),
/* menuWidth= */ menuWidth,
/* showImmersiveOption= */ Flags.enableFullyImmersiveInDesktop()
&& TaskInfoKt.getRequestingImmersive(mTaskInfo),
@@ -1389,7 +1326,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
&& mMinimumInstancesFound;
final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
.shouldShowChangeAspectRatioButton(mTaskInfo);
- final boolean inDesktopImmersive = mDesktopRepository
+ final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId)
.isTaskInFullImmersiveState(mTaskInfo.taskId);
final boolean isBrowserApp = isBrowserApp();
mHandleMenu = mHandleMenuFactory.create(
@@ -1427,8 +1364,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
/* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
/* openInBrowserClickListener= */ (intent) -> {
mOpenInBrowserClickListener.accept(intent);
- onCapturedLinkExpired();
- if (Flags.enableDesktopWindowingAppToWebEducation()) {
+ onCapturedLinkUsed();
+ if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
mWindowDecorCaptionHandleRepository.onAppToWebUsage();
}
return Unit.INSTANCE;
@@ -1469,7 +1406,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDisplayController,
mRootTaskDisplayAreaOrganizer,
mContext,
- mDesktopRepository,
+ mDesktopUserRepositories,
mSurfaceControlBuilderSupplier,
mSurfaceControlTransactionSupplier,
snapshotList,
@@ -1681,7 +1618,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
/** Returns true if at least one education flag is enabled. */
private boolean isEducationEnabled() {
return Flags.enableDesktopWindowingAppHandleEducation()
- || Flags.enableDesktopWindowingAppToWebEducation();
+ || Flags.enableDesktopWindowingAppToWebEducationIntegration();
}
@Override
@@ -1692,7 +1629,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
- clearCurrentViewHostRunnable();
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyNoCaptionHandle();
}
@@ -1765,7 +1701,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
final boolean inFullImmersive =
- mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId);
+ mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+ .isTaskInFullImmersiveState(mTaskInfo.taskId);
asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
mTaskInfo,
TaskInfoKt.getRequestingImmersive(mTaskInfo),
@@ -1793,8 +1730,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return !animatingTaskResizeOrReposition;
}
final boolean inImmersiveAndRequesting =
- mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId)
- && TaskInfoKt.getRequestingImmersive(mTaskInfo);
+ mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+ .isTaskInFullImmersiveState(mTaskInfo.taskId)
+ && TaskInfoKt.getRequestingImmersive(mTaskInfo);
return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting;
}
@@ -1815,7 +1753,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Context userContext,
DisplayController displayController,
SplitScreenController splitScreenController,
- DesktopRepository desktopRepository,
+ DesktopUserRepositories desktopUserRepositories,
ShellTaskOrganizer taskOrganizer,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
@@ -1827,6 +1765,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
@@ -1835,7 +1775,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
userContext,
displayController,
splitScreenController,
- desktopRepository,
+ desktopUserRepositories,
taskOrganizer,
taskInfo,
taskSurface,
@@ -1847,6 +1787,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
windowDecorCaptionHandleRepository,
desktopModeEventLogger);
@@ -1857,16 +1798,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
static class CapturedLink {
private final long mTimeStamp;
private final Uri mUri;
- private boolean mExpired;
+ private boolean mUsed;
CapturedLink(@NonNull Uri uri, long timeStamp) {
mUri = uri;
mTimeStamp = timeStamp;
- mExpired = false;
}
- void setExpired() {
- mExpired = true;
+ private void setUsed() {
+ mUsed = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index a1e329af1543..1f03d7568130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -37,6 +37,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -44,6 +45,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -53,6 +55,9 @@ import java.util.function.Supplier;
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private static final long LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10L);
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -106,8 +111,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
mRepositionStartPoint.set(x, y);
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_RESIZE_WINDOW));
if (!mDesktopWindowDecoration.mHasGlobalFocus) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */,
@@ -153,8 +158,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
// Begin window drag CUJ instrumentation only when drag position moves.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_DRAG_WINDOW));
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
@@ -207,6 +212,14 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
}
+ private InteractionJankMonitor.Configuration.Builder createLongTimeoutJankConfigBuilder(
+ @Cuj.CujType int cujType) {
+ return InteractionJankMonitor.Configuration.Builder
+ .withSurface(cujType, mDesktopWindowDecoration.mContext,
+ mDesktopWindowDecoration.mTaskSurface, mHandler)
+ .setTimeout(LONG_CUJ_TIMEOUT_MS);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
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 852eee5f6672..5d1bedb85b5e 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
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
@@ -62,6 +61,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import java.util.ArrayList;
@@ -89,10 +90,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
implements AutoCloseable {
/**
- * The Z-order of {@link #mCaptionContainerSurface}.
+ * The Z-order of the caption surface.
* <p>
* We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
- * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * layering it in front of the caption surface, we can allow it to handle input
* prior to caption view itself, treating corner inputs as resize events rather than
* repositioning.
*/
@@ -101,7 +102,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* The Z-order of the task input sink in {@link DragPositioningCallback}.
* <p>
* This task input sink is used to prevent undesired dispatching of motion events out of task
- * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * bounds; by layering it behind the caption surface, we allow captions to handle
* input events first.
*/
static final int INPUT_SINK_Z_ORDER = -2;
@@ -110,6 +111,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* Invalid corner radius that signifies that corner radius should not be set.
*/
static final int INVALID_CORNER_RADIUS = -1;
+ /**
+ * Invalid corner radius that signifies that shadow radius should not be set.
+ */
+ static final int INVALID_SHADOW_RADIUS = -1;
/**
* System-wide context. Only used to create context with overridden configurations.
@@ -123,6 +128,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+ @NonNull private final WindowDecorViewHostSupplier<WindowDecorViewHost>
+ mWindowDecorViewHostSupplier;
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
@@ -147,9 +154,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
Display mDisplay;
SurfaceControl mDecorationContainerSurface;
- SurfaceControl mCaptionContainerSurface;
- private CaptionWindowlessWindowManager mCaptionWindowManager;
- private SurfaceControlViewHost mViewHost;
+ private WindowDecorViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
boolean mIsCaptionVisible;
@@ -170,11 +175,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface) {
- this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
- SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
+ SurfaceControl taskSurface,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ this(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
+ new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
+ new DesktopModeEventLogger());
}
WindowDecoration(
@@ -189,6 +196,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
@NonNull DesktopModeEventLogger desktopModeEventLogger
) {
mContext = context;
@@ -202,6 +210,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
mIsStatusBarVisible = insetsState != null
@@ -237,15 +246,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
- updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult);
- if (outResult.mRootView != null) {
- updateViewHost(params, startT, outResult);
- }
- }
-
- protected void updateViewsAndSurfaces(RelayoutParams params,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) {
+ Trace.beginSection("WindowDecoration#relayout");
outResult.reset();
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
@@ -260,17 +261,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (params.mSetTaskVisibilityPositionAndCrop) {
finishT.hide(mTaskSurface);
}
+ Trace.endSection(); // WindowDecoration#relayout
return;
}
-
+ Trace.beginSection("WindowDecoration#relayout-inflateIfNeeded");
inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult);
- if (outResult.mRootView == null) {
- // Didn't manage to create a root view, early out.
+ Trace.endSection();
+ final boolean hasCaptionView = outResult.mRootView != null;
+ if (!hasCaptionView) {
+ Trace.endSection(); // WindowDecoration#relayout
return;
}
- rootView = null; // Clear it just in case we use it accidentally
+ Trace.beginSection("WindowDecoration#relayout-updateCaptionVisibility");
updateCaptionVisibility(outResult.mRootView, params);
+ Trace.endSection();
final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
outResult.mWidth = taskBounds.width();
@@ -285,10 +290,65 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mCaptionY = 0;
outResult.mCaptionTopPadding = params.mCaptionTopPadding;
+ Trace.beginSection("relayout-createViewHostIfNeeded");
+ createViewHostIfNeeded(mDecorWindowContext, mDisplay);
+ Trace.endSection();
+
+ Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets");
+ final SurfaceControl captionSurface = mViewHost.getSurfaceControl();
updateDecorationContainerSurface(startT, outResult);
- updateCaptionContainerSurface(startT, outResult);
+ updateCaptionContainerSurface(captionSurface, startT, outResult);
updateCaptionInsets(params, wct, outResult, taskBounds);
updateTaskSurface(params, startT, finishT, outResult);
+ Trace.endSection();
+
+ Trace.beginSection("WindowDecoration#relayout-updateViewHost");
+ outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+ final Rect localCaptionBounds = new Rect(
+ outResult.mCaptionX,
+ outResult.mCaptionY,
+ outResult.mCaptionX + outResult.mCaptionWidth,
+ outResult.mCaptionY + outResult.mCaptionHeight);
+ final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
+ ? calculateLimitedTouchableRegion(params, localCaptionBounds)
+ : null;
+ updateViewHierarchy(params, outResult, startT, touchableRegion);
+ Trace.endSection();
+
+ Trace.endSection(); // WindowDecoration#relayout
+ }
+
+ private void createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
+ if (mViewHost == null) {
+ mViewHost = mWindowDecorViewHostSupplier.acquire(context, display);
+ }
+ }
+
+ private void updateViewHierarchy(@NonNull RelayoutParams params,
+ @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT,
+ @Nullable Region touchableRegion) {
+ Trace.beginSection("WindowDecoration#updateViewHierarchy");
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(
+ outResult.mCaptionWidth,
+ outResult.mCaptionHeight,
+ TYPE_APPLICATION,
+ FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSPARENT);
+ lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ lp.inputFeatures = params.mInputFeatures;
+ if (params.mAsyncViewHost) {
+ if (params.mApplyStartTransactionOnDraw) {
+ throw new IllegalArgumentException("Cannot use sync draw tx with async relayout");
+ }
+ mViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion);
+ } else {
+ mViewHost.updateView(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion, params.mApplyStartTransactionOnDraw ? startT : null);
+ }
+ Trace.endSection();
}
private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct,
@@ -356,23 +416,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.show(mDecorationContainerSurface);
}
- private void updateCaptionContainerSurface(
+ private void updateCaptionContainerSurface(@NonNull SurfaceControl captionSurface,
SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
- if (mCaptionContainerSurface == null) {
- final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
- mCaptionContainerSurface = builder
- .setName("Caption container of Task=" + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mDecorationContainerSurface)
- .setCallsite("WindowDecoration.updateCaptionContainerSurface")
- .build();
- }
-
- startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
- outResult.mCaptionHeight)
- .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
- .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
- .show(mCaptionContainerSurface);
+ startT.reparent(captionSurface, mDecorationContainerSurface)
+ .setWindowCrop(captionSurface, outResult.mCaptionWidth, outResult.mCaptionHeight)
+ .setPosition(captionSurface, outResult.mCaptionX, 0 /* y */)
+ .setLayer(captionSurface, CAPTION_LAYER_Z_ORDER)
+ .show(captionSurface);
}
private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
@@ -439,16 +489,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
}
- float shadowRadius;
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- // Shadow is not needed for fullscreen tasks
- shadowRadius = 0;
- } else {
- shadowRadius =
- loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
+ if (params.mShadowRadius != INVALID_SHADOW_RADIUS) {
+ startT.setShadowRadius(mTaskSurface, params.mShadowRadius);
+ finishT.setShadowRadius(mTaskSurface, params.mShadowRadius);
}
- startT.setShadowRadius(mTaskSurface, shadowRadius);
- finishT.setShadowRadius(mTaskSurface, shadowRadius);
if (params.mSetTaskVisibilityPositionAndCrop) {
startT.show(mTaskSurface);
@@ -472,80 +516,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
- /**
- * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our
- * View hierarchy.
- *
- * @param params parameters to use from the last relayout
- * @param onDrawTransaction a transaction to apply in sync with #onDraw
- * @param outResult results to use from the last relayout
- *
- */
- protected void updateViewHost(RelayoutParams params,
- SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) {
- Trace.beginSection("CaptionViewHostLayout");
- if (mCaptionWindowManager == null) {
- // Put caption under a container surface because ViewRootImpl sets the destination frame
- // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowlessWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
- }
- mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
- final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(
- outResult.mCaptionWidth,
- outResult.mCaptionHeight,
- TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSPARENT);
- lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
- lp.setTrustedOverlay();
- lp.inputFeatures = params.mInputFeatures;
- final Rect localCaptionBounds = new Rect(
- outResult.mCaptionX,
- outResult.mCaptionY,
- outResult.mCaptionX + outResult.mCaptionWidth,
- outResult.mCaptionY + outResult.mCaptionHeight);
- final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
- ? calculateLimitedTouchableRegion(params, localCaptionBounds)
- : null;
- if (mViewHost == null) {
- Trace.beginSection("CaptionViewHostLayout-new");
- mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
- mCaptionWindowManager);
- if (params.mApplyStartTransactionOnDraw) {
- if (onDrawTransaction == null) {
- throw new IllegalArgumentException("Trying to sync a null Transaction");
- }
- mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
- }
- outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
- if (params.mLimitTouchRegionToSystemAreas) {
- mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
- }
- mViewHost.setView(outResult.mRootView, lp);
- Trace.endSection();
- } else {
- Trace.beginSection("CaptionViewHostLayout-relayout");
- if (params.mApplyStartTransactionOnDraw) {
- if (onDrawTransaction == null) {
- throw new IllegalArgumentException("Trying to sync a null Transaction");
- }
- mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
- }
- outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
- if (params.mLimitTouchRegionToSystemAreas) {
- mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
- }
- mViewHost.relayout(lp);
- Trace.endSection();
- }
- if (touchableRegion != null) {
- touchableRegion.recycle();
- }
- Trace.endSection(); // CaptionViewHostLayout
- }
-
@NonNull
private Region calculateLimitedTouchableRegion(
RelayoutParams params,
@@ -694,18 +664,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
void releaseViews(WindowContainerTransaction wct) {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- mCaptionWindowManager = null;
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
boolean released = false;
- if (mCaptionContainerSurface != null) {
- t.remove(mCaptionContainerSurface);
- mCaptionContainerSurface = null;
+ if (mViewHost != null) {
+ mWindowDecorViewHostSupplier.release(mViewHost, t);
+ mViewHost = null;
released = true;
}
@@ -851,13 +814,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
@InsetsSource.Flags int mInsetSourceFlags;
final Region mDisplayExclusionRegion = Region.obtain();
- int mShadowRadiusId;
- int mCornerRadius;
+ int mShadowRadius = INVALID_SHADOW_RADIUS;
+ int mCornerRadius = INVALID_CORNER_RADIUS;
int mCaptionTopPadding;
boolean mIsCaptionVisible;
Configuration mWindowDecorConfig;
+ boolean mAsyncViewHost;
boolean mApplyStartTransactionOnDraw;
boolean mSetTaskVisibilityPositionAndCrop;
@@ -874,8 +838,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mInsetSourceFlags = 0;
mDisplayExclusionRegion.setEmpty();
- mShadowRadiusId = Resources.ID_NULL;
- mCornerRadius = 0;
+ mShadowRadius = INVALID_SHADOW_RADIUS;
+ mCornerRadius = INVALID_SHADOW_RADIUS;
mCaptionTopPadding = 0;
mIsCaptionVisible = false;
@@ -883,6 +847,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mApplyStartTransactionOnDraw = false;
mSetTaskVisibilityPositionAndCrop = false;
mWindowDecorConfig = null;
+ mAsyncViewHost = false;
mHasGlobalFocus = false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
new file mode 100644
index 000000000000..a205ac662ab1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter].
+ *
+ * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * case the update work will be posted on the [ShellMainThread] with no delay.
+ */
+class DefaultWindowDecorViewHost(
+ context: Context,
+ @ShellMainThread private val mainScope: CoroutineScope,
+ display: Display,
+ @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
+ SurfaceControlViewHostAdapter(context, display),
+) : WindowDecorViewHost {
+ private var currentUpdateJob: Job? = null
+
+ override val surfaceControl: SurfaceControl
+ get() = viewHostAdapter.rootSurface
+
+ override fun updateView(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ onDrawTransaction: SurfaceControl.Transaction?,
+ ) {
+ Trace.beginSection("DefaultWindowDecorViewHost#updateView")
+ clearCurrentUpdateJob()
+ updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction)
+ Trace.endSection()
+ }
+
+ override fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ ) {
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
+ clearCurrentUpdateJob()
+ currentUpdateJob =
+ mainScope.launch {
+ updateViewHost(
+ view,
+ attrs,
+ configuration,
+ touchableRegion,
+ onDrawTransaction = null
+ )
+ }
+ Trace.endSection()
+ }
+
+ override fun release(t: SurfaceControl.Transaction) {
+ clearCurrentUpdateJob()
+ viewHostAdapter.release(t)
+ }
+
+ private fun updateViewHost(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ onDrawTransaction: SurfaceControl.Transaction?,
+ ) {
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
+ viewHostAdapter.prepareViewHost(configuration, touchableRegion)
+ onDrawTransaction?.let {
+ viewHostAdapter.applyTransactionOnDraw(it)
+ }
+ viewHostAdapter.updateView(view, attrs)
+ Trace.endSection()
+ }
+
+ private fun clearCurrentUpdateJob() {
+ currentUpdateJob?.cancel()
+ currentUpdateJob = null
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
new file mode 100644
index 000000000000..7821619e61d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
+ */
+class DefaultWindowDecorViewHostSupplier(
+ @ShellMainThread private val mainScope: CoroutineScope
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
+
+ override fun acquire(context: Context, display: Display): WindowDecorViewHost {
+ return DefaultWindowDecorViewHost(context, mainScope, display)
+ }
+
+ override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) {
+ viewHost.release(t)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt
new file mode 100644
index 000000000000..adb0ba643e0d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.os.Trace
+import android.util.Pools
+import android.view.Display
+import android.view.SurfaceControl
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be
+ * expensive to recreate for each new or updated window decoration.
+ *
+ * Callers can obtain a [WindowDecorViewHost] using [acquire], which will return a pooled
+ * object if available, or create a new instance and return it if needed. When finished using a
+ * [WindowDecorViewHost], it must be released using [release] to allow it to be sent back
+ * into the pool and reused later on.
+ */
+class PooledWindowDecorViewHostSupplier(
+ @ShellMainThread private val mainScope: CoroutineScope,
+ maxPoolSize: Int,
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
+
+ private val pool: Pools.Pool<WindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize)
+ private var nextDecorViewHostId = 0
+
+ override fun acquire(context: Context, display: Display): WindowDecorViewHost {
+ val pooledViewHost = pool.acquire()
+ if (pooledViewHost != null) {
+ return pooledViewHost
+ }
+ Trace.beginSection("PooledWindowDecorViewHostSupplier#acquire-newInstance")
+ val newDecorViewHost = newInstance(context, display)
+ Trace.endSection()
+ return newDecorViewHost
+ }
+
+ override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) {
+ val pooled = pool.release(viewHost)
+ if (!pooled) {
+ viewHost.release(t)
+ }
+ }
+
+ private fun newInstance(context: Context, display: Display): ReusableWindowDecorViewHost {
+ // Use a reusable window decor view host, as it allows swapping the entire view hierarchy.
+ return ReusableWindowDecorViewHost(
+ context = context,
+ mainScope = mainScope,
+ display = display,
+ id = nextDecorViewHostId++
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
new file mode 100644
index 000000000000..bf0b1186254f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * An implementation of [WindowDecorViewHost] that supports:
+ * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be called with
+ * different [View] instances. This is useful when reusing [WindowDecorViewHost]s instances for
+ * vastly different view hierarchies, such as Desktop Windowing's App Handles and App Headers.
+ */
+class ReusableWindowDecorViewHost(
+ private val context: Context,
+ @ShellMainThread private val mainScope: CoroutineScope,
+ display: Display,
+ val id: Int,
+ @VisibleForTesting
+ val viewHostAdapter: SurfaceControlViewHostAdapter =
+ SurfaceControlViewHostAdapter(context, display),
+) : WindowDecorViewHost {
+ @VisibleForTesting val rootView = FrameLayout(context)
+
+ private var currentUpdateJob: Job? = null
+
+ override val surfaceControl: SurfaceControl
+ get() = viewHostAdapter.rootSurface
+
+ override fun updateView(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ onDrawTransaction: SurfaceControl.Transaction?,
+ ) {
+ Trace.beginSection("ReusableWindowDecorViewHost#updateView")
+ clearCurrentUpdateJob()
+ updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction)
+ Trace.endSection()
+ }
+
+ override fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ ) {
+ Trace.beginSection("ReusableWindowDecorViewHost#updateViewAsync")
+ clearCurrentUpdateJob()
+ currentUpdateJob =
+ mainScope.launch {
+ updateViewHost(
+ view,
+ attrs,
+ configuration,
+ touchableRegion,
+ onDrawTransaction = null,
+ )
+ }
+ Trace.endSection()
+ }
+
+ override fun release(t: SurfaceControl.Transaction) {
+ clearCurrentUpdateJob()
+ viewHostAdapter.release(t)
+ }
+
+ private fun updateViewHost(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ onDrawTransaction: SurfaceControl.Transaction?,
+ ) {
+ Trace.beginSection("ReusableWindowDecorViewHost#updateViewHost")
+ viewHostAdapter.prepareViewHost(configuration, touchableRegion)
+ onDrawTransaction?.let { viewHostAdapter.applyTransactionOnDraw(it) }
+ rootView.removeAllViews()
+ rootView.addView(view)
+ viewHostAdapter.updateView(rootView, attrs)
+ Trace.endSection()
+ }
+
+ private fun clearCurrentUpdateJob() {
+ currentUpdateJob?.cancel()
+ currentUpdateJob = null
+ }
+
+ companion object {
+ private const val TAG = "ReusableWindowDecorViewHost"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
new file mode 100644
index 000000000000..26a43f4d5291
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.AttachedSurfaceControl
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+
+typealias SurfaceControlViewHostFactory =
+ (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
+
+/**
+ * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl].
+ *
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView], only
+ * its properties and binding may be changed, children views may be added, removed or changed
+ * and its [WindowManager.LayoutParams] may be changed.
+ */
+class SurfaceControlViewHostAdapter(
+ private val context: Context,
+ private val display: Display,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+ SurfaceControlViewHost(c, d, wwm, s)
+ },
+) {
+ val rootSurface: SurfaceControl =
+ SurfaceControl.Builder()
+ .setName("SurfaceControlViewHostAdapter surface")
+ .setContainerLayer()
+ .setCallsite("SurfaceControlViewHostAdapter#init")
+ .build()
+
+ private var wwm: WindowDecorWindowlessWindowManager? = null
+ @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
+
+ /**
+ * Initialize or updates the [SurfaceControlViewHost].
+ */
+ fun prepareViewHost(
+ configuration: Configuration,
+ touchableRegion: Region?
+ ) {
+ if (wwm == null) {
+ wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
+ }
+ if (viewHost == null) {
+ viewHost =
+ surfaceControlViewHostFactory.invoke(
+ context,
+ display,
+ requireWindowlessWindowManager(),
+ "SurfaceControlViewHostAdapter#prepareViewHost",
+ )
+ }
+ requireWindowlessWindowManager().setConfiguration(configuration)
+ requireWindowlessWindowManager().setTouchRegion(requireViewHost(), touchableRegion)
+ }
+
+ /**
+ * Request to apply the transaction atomically with the next draw of the view hierarchy. See
+ * [AttachedSurfaceControl.applyTransactionOnDraw].
+ */
+ fun applyTransactionOnDraw(t: SurfaceControl.Transaction) {
+ requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t)
+ }
+
+ /** Update the view hierarchy of the view host. */
+ fun updateView(view: View, attrs: WindowManager.LayoutParams) {
+ if (requireViewHost().view == null) {
+ Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView")
+ requireViewHost().setView(view, attrs)
+ Trace.endSection()
+ } else {
+ check(requireViewHost().view == view) { "Changing view is not allowed" }
+ Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout")
+ requireViewHost().relayout(attrs)
+ Trace.endSection()
+ }
+ }
+
+ /** Release the view host and remove the backing surface. */
+ fun release(t: SurfaceControl.Transaction) {
+ viewHost?.release()
+ t.remove(rootSurface)
+ }
+
+ /** Whether the view host has had a view hierarchy set. */
+ fun isInitialized(): Boolean = viewHost?.view != null
+
+ private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
+ return wwm ?: error("Expected non-null windowless window manager")
+ }
+
+ private fun requireViewHost(): SurfaceControlViewHost {
+ return viewHost ?: error("Expected non-null view host")
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
new file mode 100644
index 000000000000..2dcbbac4646f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import com.android.wm.shell.windowdecor.WindowDecoration
+
+/**
+ * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a
+ * [SurfaceControl].
+ */
+interface WindowDecorViewHost {
+ /** The surface where the underlying [View] hierarchy is being rendered. */
+ val surfaceControl: SurfaceControl
+
+ /** Synchronously update the view hierarchy of this view host. */
+ fun updateView(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region? = null,
+ onDrawTransaction: SurfaceControl.Transaction? = null,
+ )
+
+ /** Asynchronously update the view hierarchy of this view host. */
+ fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region? = null,
+ )
+
+ /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
+ fun release(t: SurfaceControl.Transaction)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt
new file mode 100644
index 000000000000..00e29ecaebe3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+
+/** An interface for a supplier of [WindowDecorViewHost]s. */
+interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> {
+ /** Acquire a [WindowDecorViewHost]. */
+ fun acquire(context: Context, display: Display): T
+
+ /**
+ * Release a [WindowDecorViewHost] when it is no longer used.
+ *
+ * @param viewHost the [WindowDecorViewHost] to release
+ * @param t a transaction that may be used to remove any underlying backing [SurfaceControl]
+ * that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply the
+ * transaction. It should be applied by the owner of this supplier.
+ */
+ fun release(viewHost: T, t: SurfaceControl.Transaction)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
new file mode 100644
index 000000000000..fbe8c6c83b5c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+
+/**
+ * A [WindowlessWindowManager] for the window decor caption that allows customizing the touchable
+ * region.
+ */
+class WindowDecorWindowlessWindowManager(
+ configuration: Configuration,
+ rootSurface: SurfaceControl,
+) : WindowlessWindowManager(configuration, rootSurface, /* hostInputTransferToken= */ null) {
+
+ /** Set the view host's touchable region. */
+ fun setTouchRegion(viewHost: SurfaceControlViewHost, region: Region?) {
+ setTouchRegion(viewHost.windowToken.asBinder(), region)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index 0e40a5350a43..9db69d5c1bc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -33,6 +33,7 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
import com.android.wm.shell.transition.Transitions
@@ -48,7 +49,7 @@ class DesktopTilingDecorViewModel(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
- private val taskRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val desktopModeEventLogger: DesktopModeEventLogger,
) : DisplayChangeController.OnDisplayChangingListener {
@VisibleForTesting
@@ -81,7 +82,7 @@ class DesktopTilingDecorViewModel(
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- taskRepository,
+ desktopUserRepositories,
desktopModeEventLogger,
)
tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
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 3b5c6ca2e58a..7ceac52dd2a1 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
@@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
import com.android.wm.shell.transition.Transitions
@@ -72,7 +73,7 @@ class DesktopTilingWindowDecoration(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
- private val taskRepository: DesktopRepository,
+ private val desktopUserRepositories: DesktopUserRepositories,
private val desktopModeEventLogger: DesktopModeEventLogger,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) :
@@ -630,6 +631,7 @@ class DesktopTilingWindowDecoration(
private fun allTiledTasksVisible(): Boolean {
val leftTiledTask = leftTaskResizingHelper ?: return false
val rightTiledTask = rightTaskResizingHelper ?: return false
+ val taskRepository = desktopUserRepositories.current
return taskRepository.isVisibleTask(leftTiledTask.taskInfo.taskId) &&
taskRepository.isVisibleTask(rightTiledTask.taskInfo.taskId)
}
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 d9c36cc70790..f4f60d73c25c 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
@@ -62,6 +62,14 @@ import android.tools.traces.wm.TransitionType
class DesktopModeFlickerScenarios {
companion object {
+ // In DesktopMode, window snap can be done with just a single window. In this case, the
+ // divider tiling between left and right window won't be shown, and hence its states are not
+ // obtainable in test.
+ // As the test should just focus on ensuring window goes to one side of the screen, an
+ // acceptable approach is to ensure snapped window still fills > 95% of either side of the
+ // screen.
+ private const val SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO = 0.05
+
val END_DRAG_TO_DESKTOP =
FlickerConfigEntry(
scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
@@ -230,9 +238,11 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+ AppWindowCoversLeftHalfScreenAtEnd(
+ DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+ )
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val SNAP_RESIZE_RIGHT_WITH_BUTTON =
@@ -245,9 +255,11 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+ AppWindowCoversRightHalfScreenAtEnd(
+ DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+ )
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val SNAP_RESIZE_LEFT_WITH_DRAG =
@@ -260,9 +272,11 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+ AppWindowCoversLeftHalfScreenAtEnd(
+ DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+ )
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val SNAP_RESIZE_RIGHT_WITH_DRAG =
@@ -275,9 +289,11 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
- listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+ AppWindowCoversRightHalfScreenAtEnd(
+ DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+ )
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE =
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 706c63244890..1de47df78853 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 7df1675f541c..34d001c858f6 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 7df1675f541c..34d001c858f6 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 2ccffa85b5c1..a3d60207a8bd 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -66,5 +66,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ Utils.resetFreezeRecentTaskList()
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt
index 8673c464ad19..9c7de05563e1 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt
@@ -65,5 +65,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ Utils.resetFreezeRecentTaskList()
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt
index 22adf6c9ee2f..9eb29723cc7d 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt
@@ -68,5 +68,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun teardown() {
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
+ Utils.resetFreezeRecentTaskList()
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt
index 4ded148f6113..d833d91c0b4b 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt
@@ -68,5 +68,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
secondaryApp.exit(wmHelper)
thirdApp.exit(wmHelper)
fourthApp.exit(wmHelper)
+ Utils.resetFreezeRecentTaskList()
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
index c0fafef96775..4a9e73b4af58 100644
--- a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
@@ -28,7 +28,10 @@ import android.tools.flicker.rules.ArtifactSaverRule
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.flicker.rules.LaunchAppRule
import android.tools.flicker.rules.RemoveAllTasksButHomeRule
+import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import java.io.IOException
import org.junit.rules.RuleChain
object Utils {
@@ -52,4 +55,17 @@ object Utils {
.around(PressHomeRule())
.around(EnsureDeviceSettingsRule())
}
+
+ /**
+ * Resets the frozen recent tasks list (ie. commits the quickswitch to the current task and
+ * reorders the current task to the end of the recents list).
+ */
+ fun resetFreezeRecentTaskList() {
+ try {
+ UiDevice.getInstance(instrumentation)
+ .executeShellCommand("wm reset-freeze-recent-tasks")
+ } catch (e: IOException) {
+ Log.e("TestUtils", "Failed to reset frozen recent tasks list", e)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index d87c1795cf7b..9c1a8f17aeee 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 99969e71238a..02b2cec8dbdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index ddbc681f7cac..f40edaebec5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -266,5 +266,26 @@ test_module_config {
test_suites: ["device-tests"],
}
+test_module_config {
+ name: "WMShellFlickerTestsPip-nonMatchParent",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
// End breakdowns for WMShellFlickerTestsPip module
////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 19c3e4048d69..a136936c0838 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -25,7 +25,7 @@
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
<!-- Turns off Wi-fi -->
- <option name="wifi" value="off"/>
+ <option name="wifi" value="on"/>
<!-- Turns off Bluetooth -->
<option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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"/>
@@ -107,4 +109,11 @@
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
+ <!-- Enable mocking GPS location by the test app -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="appops set com.android.shell android:mock_location allow"/>
+ <option name="teardown-command"
+ value="appops set com.android.shell android:mock_location deny"/>
+ </target_preparer>
</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index 7505860709e9..34e4e744dae7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -48,6 +48,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index fd4328dee0a1..609a2849f915 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
import android.tools.flicker.subject.exceptions.IncorrectRegionException
import android.tools.flicker.subject.layers.LayerSubject
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
@@ -65,6 +66,8 @@ import kotlin.math.abs
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index d4ad4ef8a401..5698023240ab 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -22,6 +22,7 @@ import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,6 +58,8 @@ import org.junit.runners.Parameterized
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
AutoEnterPipOnGoToHomeTest(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
pipApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 53725fa046c6..880e4cd4e5f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -21,6 +21,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.closePipWindow(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index a1551b7924fe..4399a237bcbb 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -21,6 +21,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
@@ -47,6 +48,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ea5b3e5b08df..49efd1d56256 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -31,6 +31,7 @@ import android.tools.traces.component.ComponentNameMatcher
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
import com.android.wm.shell.Flags
@@ -72,6 +73,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
private val testApp = FixedOrientationAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index a109c4bba2b3..97cc9d29929c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.FixMethodOrder
@@ -53,6 +54,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.clickEnterPipButton(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 14ec303206ee..b5b7847e205d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@ import org.junit.runners.Parameterized
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
ExitPipToAppTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 8a34b5e27fdb..f9a9df43a009 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -20,6 +20,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
@@ -54,6 +55,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index 12e23285ea68..79e2e4e5a82c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.Flags
@@ -68,6 +69,7 @@ import org.junit.runners.Parameterized
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
private val secondAppForSplitScreen =
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 04016a93e53d..14ae93a81c6d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -23,6 +23,7 @@ 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 com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
@@ -37,6 +38,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.changeAspectRatio(wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 6bcaabc3b680..81162c6f53f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -49,7 +49,8 @@ class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
setup {
tapl.setEnableRotation(true)
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ pipApp.waitForPip(wmHelper)
// determine the direction of dragging to test for
isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index d82bfdd6dc2f..6118d73796a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -59,7 +59,8 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
// Launch the PIP activity and wait for it to enter PiP mode
setRotation(Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ pipApp.waitForPip(wmHelper)
// get the initial region bounds and cache them
val initRegion = pipApp.getWindowRect(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index dbc97d072f9b..61c59cc45504 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -25,6 +25,7 @@ import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
@@ -40,6 +41,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 65b60ce1022b..0867f654bcaf 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip.apps
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.junit.FlickerBuilderProvider
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -29,8 +28,6 @@ import org.junit.Test
import org.junit.runners.Parameterized
abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
- protected abstract val standardAppHelper: StandardAppHelper
-
protected abstract val permissions: Array<String>
@FlickerBuilderProvider
@@ -39,7 +36,7 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
instrumentation.uiAutomation.adoptShellPermissionIdentity()
for (permission in permissions) {
instrumentation.uiAutomation.grantRuntimePermission(
- standardAppHelper.packageName,
+ pipApp.packageName,
permission
)
}
@@ -48,18 +45,18 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
}
- /** Checks [standardAppHelper] window remains visible throughout the animation */
+ /** Checks [pipApp] window remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppWindowAlwaysVisible() {
- flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertWm { this.isAppWindowVisible(pipApp.packageNameMatcher) }
}
- /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppLayerAlwaysVisible() {
- flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayers { this.isVisible(pipApp.packageNameMatcher) }
}
/** Checks the content overlay appears then disappears during the animation */
@@ -70,39 +67,39 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
/**
- * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+ * Checks that [pipApp] window remains inside the display bounds throughout the whole
* animation
*/
@Postsubmit
@Test
override fun pipWindowRemainInsideVisibleBounds() {
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
coversAtMost(displayBounds)
}
}
/**
- * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+ * Checks that the [pipApp] layer remains inside the display bounds throughout the
* whole animation
*/
@Postsubmit
@Test
override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
flicker.assertLayersVisibleRegion(
- standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ pipApp.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
) {
coversAtMost(displayBounds)
}
}
- /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+ /** Checks that the visible region of [pipApp] always reduces during the animation */
@Postsubmit
@Test
override fun pipLayerReduces() {
flicker.assertLayers {
val pipLayerList =
this.layers {
- standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
+ pipApp.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
}
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -110,14 +107,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
}
- /** Checks that [standardAppHelper] window becomes pinned */
+ /** Checks that [pipApp] window becomes pinned */
@Postsubmit
@Test
override fun pipWindowBecomesPinned() {
flicker.assertWm {
- invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.packageNameMatcher) }
.then()
- .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+ .invoke("pipWindowIsPinned") { it.isPinned(pipApp.packageNameMatcher) }
}
}
@@ -129,14 +126,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
/**
- * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+ * Checks that the focus changes between the [pipApp] window and the launcher when
* closing the pip window
*/
@Postsubmit
@Test
override fun focusChanges() {
flicker.assertEventLog {
- this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+ this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 7b04b766a191..651c92308c04 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -29,6 +29,8 @@ import android.tools.device.apphelpers.MapsAppHelper
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+import android.tools.traces.component.ComponentRegexMatcher
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -63,7 +65,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+ override val pipApp: MapsAppHelper = MapsAppHelper(instrumentation)
override val permissions: Array<String> =
arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION)
@@ -110,23 +112,23 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
// normal app open through the Launcher All Apps
// var mapsAddressOption = "Golden Gate Bridge"
- // standardAppHelper.open()
- // standardAppHelper.doSearch(mapsAddressOption)
- // standardAppHelper.getDirections()
- // standardAppHelper.startNavigation();
+ // pipApp.open()
+ // pipApp.doSearch(mapsAddressOption)
+ // pipApp.getDirections()
+ // pipApp.startNavigation();
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
)
- standardAppHelper.waitForNavigationToStart()
+ pipApp.waitForNavigationToStart()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
teardown {
- standardAppHelper.exit(wmHelper)
+ pipApp.exit(wmHelper)
mainHandler.removeCallbacks(updateLocation)
// the main looper callback might have tried to provide a new location after the
// provider is no longer in test mode, causing a crash, this prevents it from happening
@@ -137,14 +139,14 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
- /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppLayerAlwaysVisible() {
// For Maps the transition goes through the UI mode change that adds a snapshot overlay so
// we assert only start/end layers matching the app instead.
- flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
- flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayersStart { this.isVisible(pipApp.packageNameMatcher) }
+ flicker.assertLayersEnd { this.isVisible(pipApp.packageNameMatcher) }
}
@Postsubmit
@@ -154,4 +156,15 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
+
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ ignoreLayers = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+ + ComponentRegexMatcher(Regex("Background for .* SurfaceView\\[com\\.google\\.android\\.apps\\.maps/com\\.google\\.android\\.maps\\.MapsActivity\\]\\#\\d+"))
+ )
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 691194609343..be4cd780e45e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -61,7 +61,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+ override val pipApp: NetflixAppHelper = NetflixAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
@@ -69,17 +69,17 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
)
- standardAppHelper.waitForVideoPlaying()
+ pipApp.waitForVideoPlaying()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown { standardAppHelper.exit(wmHelper) }
+ teardown { pipApp.exit(wmHelper) }
}
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -143,7 +143,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
// might go outside of bounds as we resize from landscape fullscreen to destination bounds,
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
@@ -156,7 +156,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
// since Netflix uses source rect hint, there is no PiP overlay present
- flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 5e54f30dae8a..3e4ff3075f73 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -57,23 +57,23 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+ override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
- standardAppHelper.waitForVideoPlaying()
+ pipApp.waitForVideoPlaying()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown { standardAppHelper.exit(wmHelper) }
+ teardown { pipApp.exit(wmHelper) }
}
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 159cba4a559e..2c6cb503465c 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -63,7 +63,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
YouTubeEnterPipTest(flicker) {
- override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+ override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
@@ -71,13 +71,13 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
- standardAppHelper.enterFullscreen()
- standardAppHelper.waitForVideoPlaying()
+ pipApp.enterFullscreen()
+ pipApp.waitForVideoPlaying()
}
}
@@ -101,7 +101,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
// might go outside of bounds as we resize from landscape fullscreen to destination bounds,
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
@@ -114,7 +114,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
// since YouTube uses source rect hint, there is no PiP overlay present
- flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6dd3a175da65..a72de0f6daf4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -71,7 +71,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
@Presubmit
@Test
open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+ flicker.assertLayersVisibleRegion(
+ pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ ) {
coversAtMost(displayBounds)
}
}
@@ -117,7 +119,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
+ flicker.assertEventLog {
+ this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
+ }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index c37bf3579e93..7b6625ddc429 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.PipApp
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -40,7 +41,6 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
@Rule
val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- protected val pipApp = PipAppHelper(instrumentation)
protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -63,6 +63,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
}
}
+ /**
+ * Defines the test app to run PIP flicker test.
+ */
+ protected open val pipApp: PipApp = PipAppHelper(instrumentation)
+
/** Defines the transition used to run the test */
protected open val thisTransition: FlickerBuilder.() -> Unit = {}
@@ -85,10 +90,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
/** Defines the default method of entering PiP */
protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- pipApp.launchViaIntentAndWaitForPip(
+ pipApp.launchViaIntent(
wmHelper,
stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
)
+ pipApp.waitForPip(wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
new file mode 100644
index 000000000000..c405b664e431
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.pip.nonmatchparent
+
+import android.platform.test.annotations.Presubmit
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
+import org.junit.Test
+
+/**
+ * Base test class to verify PIP exit animation with an activity layout to the bottom half of
+ * the container.
+ */
+abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) :
+ ExitPipToAppTransition(flicker) {
+
+ override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation)
+
+ @Presubmit
+ @Test
+ override fun showBothAppLayersThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun showBothAppWindowsThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppCoversFullScreenAtEnd() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppLayersDuringPipTransition() {
+ flicker.assertLayers {
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ }
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppWindowsDuringPipTransition() {
+ flicker.assertWm {
+ isAppWindowVisible(testApp)
+ .isAppWindowOnTop(pipApp)
+ .isAppWindowVisible(pipApp)
+ }
+ }
+
+ /**
+ * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit
+ * animation since the [pipApp] will use a bottom half layout.
+ */
+ @Presubmit
+ @Test
+ fun testPlusPipAppCoversWindowFrameAtEnd() {
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(pipApp).region
+ visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
new file mode 100644
index 000000000000..2a3dc07037df
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.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.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via the expand button
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and
+ * then on the expand button
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
new file mode 100644
index 000000000000..8ed9cd23005b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via an intent
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout via an intent
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+} \ No newline at end of file
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 c4954f90179c..feb3edc9dab7 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
@@ -20,6 +20,7 @@ import android.app.Instrumentation
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.traces.component.ComponentNameMatcher
@@ -102,8 +103,8 @@ object SplitScreenUtils {
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
device: UiDevice,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper,
+ primaryApp: IStandardAppHelper,
+ secondaryApp: IStandardAppHelper,
rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
@@ -117,8 +118,8 @@ object SplitScreenUtils {
fun enterSplitViaIntent(
wmHelper: WindowManagerStateHelper,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ primaryApp: IStandardAppHelper,
+ secondaryApp: IStandardAppHelper
) {
val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 310c2d725c09..ec3fe95f7bef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -54,6 +54,7 @@ public final class TestRunningTaskInfoBuilder {
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
private boolean mIsTopActivityTransparent = false;
+ private boolean mIsActivityStackTransparent = false;
private int mNumActivities = 1;
private long mLastActiveTime;
@@ -158,6 +159,12 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ public TestRunningTaskInfoBuilder setActivityStackTransparent(
+ boolean isActivityStackTransparent) {
+ mIsActivityStackTransparent = isActivityStackTransparent;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setNumActivities(int numActivities) {
mNumActivities = numActivities;
return this;
@@ -187,6 +194,7 @@ public final class TestRunningTaskInfoBuilder {
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
info.isTopActivityTransparent = mIsTopActivityTransparent;
+ info.isActivityStackTransparent = mIsActivityStackTransparent;
info.numActivities = mNumActivities;
info.lastActiveTime = mLastActiveTime;
info.userId = mUserId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index c3e396524da1..a2afd2c92d3d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -33,6 +33,8 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -86,7 +88,6 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -254,7 +255,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void instantiateController_addExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ eq(IBackAnimation.DESCRIPTOR), any(), any());
}
@Test
@@ -635,7 +636,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
releaseBackGesture();
mShellExecutor.flushAll();
- verify(mAppCallback).setHandoffHandler(any());
+ verify(mAppCallback).setHandoffHandler(notNull());
}
@Test
@@ -655,7 +656,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
releaseBackGesture();
mShellExecutor.flushAll();
- verify(mAppCallback, never()).setHandoffHandler(any());
+ verify(mAppCallback).setHandoffHandler(isNull());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 0373bbd43043..6f3a3ec4fd20 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -172,10 +172,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
mDisplayImeController.addPositionProcessor(mockPp);
- mPerDisplay.setImeInputTargetRequestedVisibility(true);
+ mPerDisplay.setImeInputTargetRequestedVisibility(true, null /* statsToken */);
verify(mockPp).onImeRequested(anyInt(), eq(true));
- mPerDisplay.setImeInputTargetRequestedVisibility(false);
+ mPerDisplay.setImeInputTargetRequestedVisibility(false, null /* statsToken */);
verify(mockPp).onImeRequested(anyInt(), eq(false));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt
new file mode 100644
index 000000000000..799b48c2504f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.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.wm.shell.common
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import java.util.function.BiConsumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoSession
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for HandlerExecutor.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:HandlerExecutorTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HandlerExecutorTest : ShellTestCase() {
+
+ class TestSetThreadPriorityFn : BiConsumer<Int, Int> {
+ var lastSetPriority = UNSET_THREAD_PRIORITY
+ private set
+ var callCount = 0
+ private set
+
+ override fun accept(tid: Int, priority: Int) {
+ lastSetPriority = priority
+ callCount++
+ }
+
+ fun reset() {
+ lastSetPriority = UNSET_THREAD_PRIORITY
+ callCount = 0
+ }
+ }
+
+ val testSetPriorityFn = TestSetThreadPriorityFn()
+
+ @Test
+ fun defaultExecutorDisallowBoost() {
+ val executor = createTestHandlerExecutor()
+
+ executor.setBoost()
+
+ assertThat(executor.isBoosted()).isFalse()
+ }
+
+ @Test
+ fun boostExecutor_resetWhenNotSet_expectNoOp() {
+ val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY)
+ val mockSession: MockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(android.os.Process::class.java)
+ .startMocking()
+
+ try {
+ // Try to reset and ensure we never try to set the thread priority
+ executor.resetBoost()
+
+ assertThat(testSetPriorityFn.callCount).isEqualTo(0)
+ assertThat(executor.isBoosted()).isFalse()
+ } finally {
+ mockSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun boostExecutor_setResetBoost_expectThreadPriorityUpdated() {
+ val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY)
+ val mockSession: MockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(android.os.Process::class.java)
+ .startMocking()
+
+ try {
+ // Boost and ensure the boosted thread priority is requested
+ executor.setBoost()
+
+ assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY)
+ assertThat(testSetPriorityFn.callCount).isEqualTo(1)
+ assertThat(executor.isBoosted()).isTrue()
+
+ // Reset and ensure the default thread priority is requested
+ executor.resetBoost()
+
+ assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY)
+ assertThat(testSetPriorityFn.callCount).isEqualTo(2)
+ assertThat(executor.isBoosted()).isFalse()
+ } finally {
+ mockSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun boostExecutor_overlappingBoost_expectResetOnlyWhenNotOverlapping() {
+ val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY)
+ val mockSession: MockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(android.os.Process::class.java)
+ .startMocking()
+
+ try {
+ // Set and ensure we only update the thread priority once
+ executor.setBoost()
+ executor.setBoost()
+
+ assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY)
+ assertThat(testSetPriorityFn.callCount).isEqualTo(1)
+ assertThat(executor.isBoosted()).isTrue()
+
+ // Reset and ensure we are still boosted and the thread priority doesn't change
+ executor.resetBoost()
+
+ assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY)
+ assertThat(testSetPriorityFn.callCount).isEqualTo(1)
+ assertThat(executor.isBoosted()).isTrue()
+
+ // Reset again and ensure we update the thread priority accordingly
+ executor.resetBoost()
+
+ assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY)
+ assertThat(testSetPriorityFn.callCount).isEqualTo(2)
+ assertThat(executor.isBoosted()).isFalse()
+ } finally {
+ mockSession.finishMocking()
+ }
+ }
+
+ /**
+ * Creates a test handler executor backed by a mocked handler thread.
+ */
+ private fun createTestHandlerExecutor(
+ defaultThreadPriority: Int = DEFAULT_THREAD_PRIORITY,
+ boostedThreadPriority: Int = DEFAULT_THREAD_PRIORITY
+ ) : HandlerExecutor {
+ val handler = mock(Handler::class.java)
+ val looper = mock(Looper::class.java)
+ val thread = mock(HandlerThread::class.java)
+ whenever(handler.looper).thenReturn(looper)
+ whenever(looper.thread).thenReturn(thread)
+ whenever(thread.threadId).thenReturn(1234)
+ val executor = HandlerExecutor(handler, defaultThreadPriority, boostedThreadPriority)
+ executor.replaceSetThreadPriorityFn(testSetPriorityFn)
+ return executor
+ }
+
+ companion object {
+ private const val UNSET_THREAD_PRIORITY = 0
+ private const val DEFAULT_THREAD_PRIORITY = 1
+ private const val BOOSTED_THREAD_PRIORITY = 1000
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index cf69704a0470..fd3d3b5b6e2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -56,6 +56,7 @@ public class DividerViewTest extends ShellTestCase {
private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock SplitState mSplitState;
private @Mock Handler mHandler;
private SplitLayout mSplitLayout;
private DividerView mDividerView;
@@ -67,7 +68,7 @@ public class DividerViewTest extends ShellTestCase {
Configuration configuration = getConfiguration();
mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler);
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index dc0f213338be..1904c43d78cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -66,6 +66,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
+ @Mock SplitState mSplitState;
@Mock Handler mHandler;
@Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@@ -83,6 +84,7 @@ public class SplitLayoutTests extends ShellTestCase {
mDisplayImeController,
mTaskOrganizer,
SplitLayout.PARALLAX_NONE,
+ mSplitState,
mHandler));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
index 7b1d27a8b823..64772d037383 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.common.transition
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.server.testutils.any
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
@@ -35,6 +34,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
import org.mockito.kotlin.never
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 1d390007d470..d52fd4fdf6c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -37,35 +37,46 @@ import org.junit.runner.RunWith
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+ fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 1
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 1
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
+ fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 2
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 0
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
+ fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = false
+ isTopActivityNoDisplay = false
numActivities = 1
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() {
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
+ numActivities = 1
}))
}
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 d5287e742c2c..67573dabf2ec 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
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -61,12 +62,16 @@ 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.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import dagger.Lazy;
+import java.util.Optional;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -130,6 +135,10 @@ public class CompatUIControllerTest extends ShellTestCase {
private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
@Mock
private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -137,7 +146,6 @@ public class CompatUIControllerTest extends ShellTestCase {
@NonNull
private CompatUIStatusManager mCompatUIStatusManager;
- private boolean mInDesktopModePredicateResult;
@Before
public void setUp() {
@@ -152,6 +160,8 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(mDesktopRepository).when(mDesktopUserRepositories).getCurrent();
+ doReturn(mDesktopRepository).when(mDesktopUserRepositories).getProfile(anyInt());
doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
@@ -164,7 +174,7 @@ public class CompatUIControllerTest extends ShellTestCase {
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
- mCompatUIStatusManager, i -> mInDesktopModePredicateResult) {
+ mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -707,13 +717,17 @@ public class CompatUIControllerTest extends ShellTestCase {
@RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
@EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
- mInDesktopModePredicateResult = false;
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
verify(mController, never()).removeLayouts(taskInfo.taskId);
- mInDesktopModePredicateResult = true;
+ when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2);
+
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
verify(mController).removeLayouts(taskInfo.taskId);
}
@@ -721,13 +735,17 @@ public class CompatUIControllerTest extends ShellTestCase {
@RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
@DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
- mInDesktopModePredicateResult = false;
+ when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
verify(mController, never()).removeLayouts(taskInfo.taskId);
- mInDesktopModePredicateResult = true;
+ when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2);
+
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
verify(mController, never()).removeLayouts(taskInfo.taskId);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 94dbd112bb75..4c97c76ae122 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.compatui;
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT;
import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE;
@@ -42,10 +43,12 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -125,6 +128,9 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
@Mock private DockStateReader mDockStateReader;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
private FakeCompatUIStatusManagerTest mCompatUIStatus;
@@ -317,6 +323,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -346,6 +353,36 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
+ public void testUpdateCompatInfo_updatesLayoutCorrectlyAsync() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ assertTrue(windowManager.updateCompatInfo(
+ createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)),
+ mTaskListener, /* canShow= */ true));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100,
+ /* expectedHeight= */ 50, /* expectedExtraTopMargin= */ 0,
+ /* expectedExtraBottomMargin= */ 0);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+
+ // Window manager should be released (without animation) when eligible becomes false.
+ assertFalse(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ false),
+ mTaskListener, /* canShow= */ true));
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback, never()).accept(any());
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -375,6 +412,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -397,6 +435,29 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
+ public void testUpdateDisplayLayout_updatesLayoutCorrectlyAsync() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ int newDisplayCutoutTop = DISPLAY_CUTOUT_TOP + 7;
+ int newDisplayCutoutBottom = DISPLAY_CUTOUT_BOTTOM + 9;
+ windowManager.updateDisplayLayout(createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutTop,
+ DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutBottom)));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */
+ newDisplayCutoutTop, /* expectedExtraBottomMargin= */ newDisplayCutoutBottom);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease_animationIsCancelled() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
index 75025d9064d3..1399600d5ab9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
@@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.internal.R
import com.android.wm.shell.ShellTestCase
import java.util.function.Consumer
+import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -51,6 +52,14 @@ class LetterboxConfigurationTest : ShellTestCase() {
val COLOR_WHITE_RESOURCE_ID = android.R.color.white
@JvmStatic
val COLOR_BLACK_RESOURCE_ID = android.R.color.black
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_DEFAULT = 32
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_VALID = 16
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_NONE = 0
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_INVALID = -10
}
@Test
@@ -112,6 +121,68 @@ class LetterboxConfigurationTest : ShellTestCase() {
}
}
+ @Test
+ fun `default rounded corner radius is used if override is not set`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
+ @Test
+ fun `new rounded corner radius is used after setting a valid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ }
+ }
+
+ @Test
+ fun `no rounded corner radius is used after setting an invalid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ }
+ }
+
+ @Test
+ fun `has rounded corners for different values`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkIsLetterboxActivityCornersRounded(true)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkIsLetterboxActivityCornersRounded(true)
+ }
+ }
+
+ @Test
+ fun `reset rounded corners radius`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+
+ r.resetRoundedCornersRadius()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -135,6 +206,11 @@ class LetterboxConfigurationTest : ShellTestCase() {
.getColor(R.color.config_letterboxBackgroundColor, null)
}
+ fun setDefaultRoundedCornerRadius(radius: Int) {
+ doReturn(radius).`when`(resources)
+ .getInteger(R.integer.config_letterboxActivityCornersRadius)
+ }
+
fun loadConfiguration() {
letterboxConfig = LetterboxConfiguration(ctx)
}
@@ -147,14 +223,30 @@ class LetterboxConfigurationTest : ShellTestCase() {
letterboxConfig.resetLetterboxBackgroundColor()
}
+ fun resetRoundedCornersRadius() {
+ letterboxConfig.resetLetterboxActivityCornersRadius()
+ }
+
fun overrideBackgroundColorId(@ColorRes colorId: Int) {
letterboxConfig.setLetterboxBackgroundColorResourceId(colorId)
}
+ fun overrideRoundedCornersRadius(radius: Int) {
+ letterboxConfig.setLetterboxActivityCornersRadius(radius)
+ }
+
fun checkBackgroundColor(expected: Color) {
val colorComponents = letterboxConfig.getBackgroundColorRgbArray()
val expectedComponents = expected.components
assert(expectedComponents.contentEquals(colorComponents))
}
+
+ fun checkRoundedCornersRadius(expected: Int) {
+ assertEquals(expected, letterboxConfig.getLetterboxActivityCornersRadius())
+ }
+
+ fun checkIsLetterboxActivityCornersRounded(expected: Boolean) {
+ assertEquals(expected, letterboxConfig.isLetterboxActivityCornersRounded())
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
new file mode 100644
index 000000000000..95a0c82c76df
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Robot to test [LetterboxController] implementations.
+ */
+open class LetterboxControllerRobotTest(
+ ctx: Context,
+ controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController
+) {
+
+ companion object {
+ @JvmStatic
+ private val DISPLAY_ID = 1
+
+ @JvmStatic
+ private val TASK_ID = 20
+ }
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val surfaceBuilder: LetterboxSurfaceBuilder
+ private val letterboxController: LetterboxController
+ private val transaction: SurfaceControl.Transaction
+ private val parentLeash: SurfaceControl
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+ letterboxController = controllerBuilder(surfaceBuilder)
+ transaction = getTransactionMock()
+ parentLeash = mock<SurfaceControl>()
+ spyOn(surfaceBuilder)
+ }
+
+ fun sendCreateSurfaceRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) = letterboxController.createLetterboxSurface(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ parentLeash = parentLeash
+ )
+
+ fun sendDestroySurfaceRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) = letterboxController.destroyLetterboxSurface(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction
+ )
+
+ fun sendUpdateSurfaceVisibilityRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID,
+ visible: Boolean
+ ) = letterboxController.updateLetterboxSurfaceVisibility(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ visible = visible
+ )
+
+ fun sendUpdateSurfaceBoundsRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID,
+ taskBounds: Rect,
+ activityBounds: Rect
+ ) = letterboxController.updateLetterboxSurfaceBounds(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ taskBounds = taskBounds,
+ activityBounds = activityBounds
+ )
+
+ fun invokeDump() {
+ letterboxController.dump()
+ }
+
+ fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
+ verify(surfaceBuilder, times(times)).createSurface(
+ eq(transaction),
+ eq(parentLeash),
+ name.asAnyMode(),
+ callSite.asAnyMode(),
+ any()
+ )
+ }
+
+ fun checkTransactionRemovedInvoked(times: Int = 1) {
+ verify(transaction, times(times)).remove(any())
+ }
+
+ fun checkVisibilityUpdated(times: Int = 1, expectedVisibility: Boolean) {
+ verify(transaction, times(times)).setVisibility(any(), eq(expectedVisibility))
+ }
+
+ fun checkSurfacePositionUpdated(
+ times: Int = 1,
+ expectedX: Float = -1f,
+ expectedY: Float = -1f
+ ) {
+ verify(transaction, times(times)).setPosition(
+ any(),
+ expectedX.asAnyMode(),
+ expectedY.asAnyMode()
+ )
+ }
+
+ fun checkSurfaceSizeUpdated(times: Int = 1, expectedWidth: Int = -1, expectedHeight: Int = -1) {
+ verify(transaction, times(times)).setWindowCrop(
+ any(),
+ expectedWidth.asAnyMode(),
+ expectedHeight.asAnyMode()
+ )
+ }
+
+ fun resetTransitionTest() {
+ clearInvocations(transaction)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
new file mode 100644
index 000000000000..50fdf4510061
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [LetterboxControllerStrategy].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxControllerStrategyTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxControllerStrategyTest : ShellTestCase() {
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(true)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsSingle()
+ }
+ }
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with no rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(false)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsMultiple()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxStrategyRobotTest>) {
+ val robot = LetterboxStrategyRobotTest(mContext)
+ consumer.accept(robot)
+ }
+
+ class LetterboxStrategyRobotTest(val ctx: Context) {
+
+ companion object {
+ @JvmStatic
+ private val ROUNDED_CORNERS_TRUE = 10
+ @JvmStatic
+ private val ROUNDED_CORNERS_FALSE = 0
+ }
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val letterboxStrategy: LetterboxControllerStrategy
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ letterboxStrategy = LetterboxControllerStrategy(letterboxConfiguration)
+ }
+
+ fun configureRoundedCornerRadius(enabled: Boolean) {
+ letterboxConfiguration.setLetterboxActivityCornersRadius(
+ if (enabled) ROUNDED_CORNERS_TRUE else ROUNDED_CORNERS_FALSE
+ )
+ }
+
+ fun configureLetterboxMode() {
+ letterboxStrategy.configureLetterboxMode()
+ }
+
+ fun checkLetterboxModeIsSingle(expected: Boolean = true) {
+ val expectedMode = if (expected) SINGLE_SURFACE else MULTIPLE_SURFACES
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+
+ fun checkLetterboxModeIsMultiple(expected: Boolean = true) {
+ val expectedMode = if (expected) MULTIPLE_SURFACES else SINGLE_SURFACE
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
index 68d9bf9b926f..c37913e47cca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
@@ -28,11 +28,8 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.verify
import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
-import org.mockito.kotlin.never
import org.mockito.kotlin.times
-import org.mockito.verification.VerificationMode
/**
* Tests for [LetterboxSurfaceBuilder].
@@ -87,9 +84,7 @@ class LetterboxSurfaceBuilderTest : ShellTestCase() {
init {
letterboxConfiguration = LetterboxConfiguration(ctx)
letterboxSurfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
- tx = org.mockito.kotlin.mock<SurfaceControl.Transaction>()
- doReturn(tx).`when`(tx).setLayer(anyOrNull(), anyOrNull())
- doReturn(tx).`when`(tx).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+ tx = getTransactionMock()
parentLeash = org.mockito.kotlin.mock<SurfaceControl>()
surfaceBuilder = SurfaceControl.Builder()
spyOn(surfaceBuilder)
@@ -140,7 +135,5 @@ class LetterboxSurfaceBuilderTest : ShellTestCase() {
val components = letterboxConfiguration.getBackgroundColorRgbArray()
verify(tx, expected.asMode()).setColor(anyOrNull(), eq(components))
}
-
- private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
new file mode 100644
index 000000000000..2c06dfda7917
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.verification.VerificationMode
+
+/**
+ * @return A [SurfaceControl.Transaction] mock supporting chaining for some operations. Please
+ * add other operations if needed.
+ */
+fun getTransactionMock(): SurfaceControl.Transaction = mock<SurfaceControl.Transaction>().apply {
+ doReturn(this).`when`(this).setLayer(anyOrNull(), anyOrNull())
+ doReturn(this).`when`(this).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+ doReturn(this).`when`(this).setPosition(anyOrNull(), any(), any())
+ doReturn(this).`when`(this).setWindowCrop(anyOrNull(), any(), any())
+}
+
+// Utility to make verification mode depending on a [Boolean].
+fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+
+// Utility matchers to use for the main types as Mockito [VerificationMode].
+object LetterboxMatchers {
+ fun Int.asAnyMode() = asAnyMode { this < 0 }
+ fun Float.asAnyMode() = asAnyMode { this < 0f }
+ fun String.asAnyMode() = asAnyMode { this.isEmpty() }
+}
+
+private inline fun <reified T : Any> T.asAnyMode(condition: () -> Boolean) =
+ (if (condition()) any() else eq(this))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 9c6afcb8be63..78bb721d1028 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -25,9 +25,12 @@ import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.transition.TransitionStateHolder
+import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.TransitionObserverInputBuilder
@@ -37,12 +40,11 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.verification.VerificationMode
/**
* Tests for [LetterboxTransitionObserver].
@@ -94,6 +96,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
validateOutput {
r.creationEventDetected(expected = false)
+ r.configureStrategyInvoked(expected = false)
r.visibilityEventDetected(expected = false)
r.destroyEventDetected(expected = false)
r.updateSurfaceBoundsEventDetected(expected = false)
@@ -121,6 +124,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
validateOutput {
r.creationEventDetected(expected = true)
+ r.configureStrategyInvoked(expected = true)
r.visibilityEventDetected(expected = true, visible = true)
r.destroyEventDetected(expected = false)
r.updateSurfaceBoundsEventDetected(
@@ -154,21 +158,38 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
}
@Test
- fun `When closing change letterbox surface destroy is triggered`() {
+ fun `When closing change with no recents running letterbox surfaces are destroyed`() {
runTestScenario { r ->
executeTransitionObserverTest(observerFactory = r.observerFactory) {
r.invokeShellInit()
inputBuilder {
buildTransitionInfo()
+ r.configureRecentsState(running = false)
r.createClosingChange(inputBuilder = this)
}
validateOutput {
r.destroyEventDetected(expected = true)
- r.creationEventDetected(expected = false)
- r.visibilityEventDetected(expected = false, visible = false)
- r.updateSurfaceBoundsEventDetected(expected = false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `When closing change and recents are running letterbox surfaces are not destroyed`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+
+ inputBuilder {
+ buildTransitionInfo()
+ r.createClosingChange(inputBuilder = this)
+ r.configureRecentsState(running = true)
+ }
+
+ validateOutput {
+ r.destroyEventDetected(expected = false)
}
}
}
@@ -197,6 +218,8 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
private val transitions: Transitions
private val letterboxController: LetterboxController
private val letterboxObserver: LetterboxTransitionObserver
+ private val transitionStateHolder: TransitionStateHolder
+ private val letterboxStrategy: LetterboxControllerStrategy
val observerFactory: () -> LetterboxTransitionObserver
@@ -205,8 +228,18 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
shellInit = ShellInit(executor)
transitions = mock<Transitions>()
letterboxController = mock<LetterboxController>()
+ letterboxStrategy = mock<LetterboxControllerStrategy>()
+ transitionStateHolder =
+ TransitionStateHolder(shellInit, mock<RecentsTransitionHandler>())
+ spyOn(transitionStateHolder)
letterboxObserver =
- LetterboxTransitionObserver(shellInit, transitions, letterboxController)
+ LetterboxTransitionObserver(
+ shellInit,
+ transitions,
+ letterboxController,
+ transitionStateHolder,
+ letterboxStrategy
+ )
observerFactory = { letterboxObserver }
}
@@ -218,6 +251,10 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
verify(transitions, expected.asMode()).registerObserver(observer())
}
+ fun configureRecentsState(running: Boolean) {
+ doReturn(running).`when`(transitionStateHolder).isRecentsTransitionRunning()
+ }
+
fun creationEventDetected(
expected: Boolean,
displayId: Int = DISPLAY_ID,
@@ -258,16 +295,21 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
expected: Boolean,
displayId: Int = DISPLAY_ID,
taskId: Int = TASK_ID,
- taskBounds: Rect = Rect()
+ taskBounds: Rect = Rect(),
+ activityBounds: Rect = Rect()
) = verify(
letterboxController,
expected.asMode()
).updateLetterboxSurfaceBounds(
eq(LetterboxKey(displayId, taskId)),
any<SurfaceControl.Transaction>(),
- eq(taskBounds)
+ eq(taskBounds),
+ eq(activityBounds)
)
+ fun configureStrategyInvoked(expected: Boolean) =
+ verify(letterboxStrategy, expected.asMode()).configureLetterboxMode()
+
fun createTopActivityChange(
inputBuilder: TransitionObserverInputBuilder,
isLetterboxed: Boolean = true,
@@ -300,7 +342,5 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
this.displayId = displayId
}, changeMode = TRANSIT_CLOSE)
}
-
- private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
new file mode 100644
index 000000000000..06b805233ee7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [LetterboxUtils].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxUtilsTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxUtilsTest : ShellTestCase() {
+
+ val firstLetterboxController = mock<LetterboxController>()
+ val secondLetterboxController = mock<LetterboxController>()
+ val thirdLetterboxController = mock<LetterboxController>()
+
+ private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+ { _ ->
+ firstLetterboxController.append(secondLetterboxController)
+ .append(thirdLetterboxController)
+ }
+
+ @Test
+ fun `Appended LetterboxController invoked creation on all the controllers`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+
+ r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController)
+ r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController)
+ r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController)
+ }
+ }
+
+ @Test
+ fun `Appended LetterboxController invoked destroy on all the controllers`() {
+ runTestScenario { r ->
+ r.sendDestroySurfaceRequest()
+ r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController)
+ r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController)
+ r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController)
+ }
+ }
+
+ @Test
+ fun `Appended LetterboxController invoked update visibility on all the controllers`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceVisibilityRequest(visible = true)
+ r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController)
+ r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController)
+ r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController)
+ }
+ }
+
+ @Test
+ fun `Appended LetterboxController invoked update bounds on all the controllers`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect())
+ r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController)
+ r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController)
+ r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController)
+ }
+ }
+
+ @Test
+ fun `Appended LetterboxController invoked update dump on all the controllers`() {
+ runTestScenario { r ->
+ r.invokeDump()
+ r.verifyDumpInvoked(target = firstLetterboxController)
+ r.verifyDumpInvoked(target = secondLetterboxController)
+ r.verifyDumpInvoked(target = thirdLetterboxController)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) {
+ val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder)
+ consumer.accept(robot)
+ }
+
+ class AppendLetterboxControllerRobotTest(
+ ctx: Context,
+ builder: (LetterboxSurfaceBuilder) -> LetterboxController
+ ) : LetterboxControllerRobotTest(ctx, builder) {
+
+ fun verifyCreateSurfaceInvokedWithRequest(
+ target: LetterboxController,
+ times: Int = 1
+ ) {
+ verify(target, times(times)).createLetterboxSurface(any(), any(), any())
+ }
+
+ fun verifyDestroySurfaceInvokedWithRequest(
+ target: LetterboxController,
+ times: Int = 1
+ ) {
+ verify(target, times(times)).destroyLetterboxSurface(any(), any())
+ }
+
+ fun verifyUpdateVisibilitySurfaceInvokedWithRequest(
+ target: LetterboxController,
+ times: Int = 1
+ ) {
+ verify(target, times(times)).updateLetterboxSurfaceVisibility(any(), any(), any())
+ }
+
+ fun verifyUpdateSurfaceBoundsInvokedWithRequest(
+ target: LetterboxController,
+ times: Int = 1
+ ) {
+ verify(target, times(times)).updateLetterboxSurfaceBounds(any(), any(), any(), any())
+ }
+
+ fun verifyDumpInvoked(
+ target: LetterboxController,
+ times: Int = 1
+ ) {
+ verify(target, times(times)).dump()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
new file mode 100644
index 000000000000..e6bff4c1ec15
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [MixedLetterboxController].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:MixedLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MixedLetterboxControllerTest : ShellTestCase() {
+
+ @Test
+ fun `When strategy is SINGLE_SURFACE and a create request is sent multi are destroyed`() {
+ runTestScenario { r ->
+ r.configureStrategyFor(LetterboxMode.SINGLE_SURFACE)
+ r.sendCreateSurfaceRequest()
+ r.checkCreateInvokedOnSingleController()
+ r.checkDestroyInvokedOnMultiController()
+ }
+ }
+
+ @Test
+ fun `When strategy is MULTIPLE_SURFACES and a create request is sent single is destroyed`() {
+ runTestScenario { r ->
+ r.configureStrategyFor(LetterboxMode.MULTIPLE_SURFACES)
+ r.sendCreateSurfaceRequest()
+ r.checkDestroyInvokedOnSingleController()
+ r.checkCreateInvokedOnMultiController()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) {
+ val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder())
+ consumer.accept(robot)
+ }
+
+ class MixedLetterboxControllerRobotTest(
+ ctx: Context,
+ private val objectToTestHolder: ObjectToTestHolder
+ ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) {
+
+ fun configureStrategyFor(letterboxMode: LetterboxMode) {
+ doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy)
+ .getLetterboxImplementationMode()
+ }
+
+ fun checkCreateInvokedOnSingleController(times: Int = 1) {
+ verify(
+ objectToTestHolder.singleLetterboxController,
+ times(times)
+ ).createLetterboxSurface(any(), any(), any())
+ }
+
+ fun checkCreateInvokedOnMultiController(times: Int = 1) {
+ verify(
+ objectToTestHolder.multipleLetterboxController,
+ times(times)
+ ).createLetterboxSurface(any(), any(), any())
+ }
+
+ fun checkDestroyInvokedOnSingleController(times: Int = 1) {
+ verify(
+ objectToTestHolder.singleLetterboxController,
+ times(times)
+ ).destroyLetterboxSurface(any(), any())
+ }
+
+ fun checkDestroyInvokedOnMultiController(times: Int = 1) {
+ verify(
+ objectToTestHolder.multipleLetterboxController,
+ times(times)
+ ).destroyLetterboxSurface(any(), any())
+ }
+ }
+
+ data class ObjectToTestHolder(
+ val singleLetterboxController: SingleSurfaceLetterboxController =
+ mock<SingleSurfaceLetterboxController>(),
+ val multipleLetterboxController: MultiSurfaceLetterboxController =
+ mock<MultiSurfaceLetterboxController>(),
+ val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>()
+ ) {
+
+ private val mixedController =
+ MixedLetterboxController(
+ singleLetterboxController,
+ multipleLetterboxController,
+ controllerStrategy
+ )
+
+ val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+ { _ -> mixedController }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
new file mode 100644
index 000000000000..295d4edf206b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [MultiSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:MultiSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MultiSurfaceLetterboxControllerTest : ShellTestCase() {
+
+ @Test
+ fun `When creation is requested the surfaces are created if not present`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `When creation is requested multiple times the surfaces are created once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `Different surfaces are created for every key`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+ r.checkSurfaceBuilderInvoked(times = 12)
+ }
+ }
+
+ @Test
+ fun `Created surface are removed once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.checkSurfaceBuilderInvoked(times = 4)
+
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+
+ r.checkTransactionRemovedInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive visibility update`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceVisibilityRequest(visible = true)
+ r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+ r.checkVisibilityUpdated(times = 4, expectedVisibility = true)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive taskBounds update`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+
+ r.checkSurfacePositionUpdated(times = 0)
+ r.checkSurfaceSizeUpdated(times = 0)
+
+ r.sendCreateSurfaceRequest()
+
+ // Pillarbox.
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+ // The Left and Top surfaces.
+ r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+ // The Right surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 1500f, expectedY = 0f)
+ // The Bottom surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1000f)
+ // Left and Right surface.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 500, expectedHeight = 1000)
+ // Top and Button.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 2000, expectedHeight = 0)
+
+ r.resetTransitionTest()
+
+ // Letterbox.
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 1000, 2000),
+ activityBounds = Rect(0, 500, 1000, 1500)
+ )
+ // Top and Left surfaces.
+ r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+ // Bottom surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1500f)
+ // Right surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 1000f, expectedY = 0f)
+
+ // Left and Right surfaces.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 0, expectedHeight = 2000)
+ // Top and Button surfaces,
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 1000, expectedHeight = 500)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+ val robot =
+ LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) })
+ consumer.accept(robot)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
new file mode 100644
index 000000000000..125e700bcd42
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [SingleSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SingleSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SingleSurfaceLetterboxControllerTest : ShellTestCase() {
+
+ @Test
+ fun `When creation is requested the surface is created if not present`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked()
+ }
+ }
+
+ @Test
+ fun `When creation is requested multiple times the surface is created once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 1)
+ }
+ }
+
+ @Test
+ fun `A different surface is created for every key`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+ r.checkSurfaceBuilderInvoked(times = 3)
+ }
+ }
+
+ @Test
+ fun `Created surface is removed once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.checkSurfaceBuilderInvoked()
+
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+
+ r.checkTransactionRemovedInvoked()
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive visibility update`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceVisibilityRequest(visible = true)
+ r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+ r.checkVisibilityUpdated(expectedVisibility = true)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive taskBounds update`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+
+ r.checkSurfacePositionUpdated(times = 0)
+ r.checkSurfaceSizeUpdated(times = 0)
+
+ r.resetTransitionTest()
+
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 0f)
+ r.checkSurfaceSizeUpdated(times = 1, expectedWidth = 2000, expectedHeight = 1000)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+ val robot =
+ LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) })
+ consumer.accept(robot)
+ }
+}
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 2ea0379e3bf7..41a594a3347a 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
@@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
import android.graphics.Rect
import android.os.Binder
+import android.os.UserManager
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
@@ -96,11 +97,12 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Mock lateinit var taskStackListener: TaskStackListenerImpl
@Mock lateinit var persistentRepository: DesktopPersistentRepository
@Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ @Mock lateinit var userManager: UserManager
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
private lateinit var testScope: CoroutineScope
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -117,13 +119,14 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository =
- DesktopRepository(
+ userRepositories =
+ DesktopUserRepositories(
context,
shellInit,
persistentRepository,
repositoryInitializer,
- testScope
+ testScope,
+ userManager
)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -132,7 +135,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
)
handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, taskRepository)
+ taskStackListener, resizeTransitionHandler, userRepositories)
shellInit.init()
}
@@ -156,7 +159,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
clearInvocations(shellInit)
handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, taskRepository)
+ taskStackListener, resizeTransitionHandler, userRepositories)
verify(shellInit, never()).addInitCallback(any(),
any<DesktopActivityOrientationChangeHandler>())
@@ -180,7 +183,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT
task.topActivityInfo = activityInfo
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- taskRepository.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
+ userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
runningTasks.add(task)
taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
@@ -203,7 +206,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
val task = setUpFreeformTask(isResizeable = false)
- taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
+ userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false)
taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
SCREEN_ORIENTATION_LANDSCAPE)
@@ -268,7 +271,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
task.topActivityInfo = activityInfo
task.isResizeable = isResizeable
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- taskRepository.addTask(displayId, task.taskId, isVisible = true)
+ userRepositories.current.addTask(displayId, task.taskId, isVisible = true)
runningTasks.add(task)
return task
}
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 b57c55c4c45a..db4c7465ae48 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
@@ -76,18 +76,19 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
@JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var mockTransitions: Transitions
- private lateinit var desktopRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayLayout: DisplayLayout
private val transactionSupplier = { StubTransaction() }
private lateinit var controller: DesktopImmersiveController
+ private lateinit var desktopRepository: DesktopRepository
@Before
fun setUp() {
- desktopRepository = DesktopRepository(
- context, ShellInit(TestShellExecutor()), mock(), mock(), mock()
+ userRepositories = DesktopUserRepositories(
+ context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock()
)
whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
.thenReturn(mockDisplayLayout)
@@ -97,12 +98,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
controller = DesktopImmersiveController(
shellInit = mock(),
transitions = mockTransitions,
- desktopRepository = desktopRepository,
+ desktopUserRepositories = userRepositories,
displayController = mockDisplayController,
shellTaskOrganizer = mockShellTaskOrganizer,
shellCommandHandler = mock(),
transactionSupplier = transactionSupplier,
)
+ desktopRepository = userRepositories.current
}
@Test
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 62717a32d99f..49a7e2951a7e 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
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WindowingMode
+import android.content.Intent
import android.os.Binder
import android.os.Handler
import android.os.IBinder
@@ -36,7 +37,9 @@ import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TransitionType
+import android.window.IWindowContainerToken
import android.window.TransitionInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
@@ -60,6 +63,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
@@ -84,7 +88,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Mock
lateinit var transitions: Transitions
@Mock
- lateinit var desktopRepository: DesktopRepository
+ lateinit var userRepositories: DesktopUserRepositories
@Mock
lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
@Mock
@@ -103,16 +107,21 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
lateinit var shellInit: ShellInit
@Mock
lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock
+ private lateinit var desktopRepository: DesktopRepository
private lateinit var mixedHandler: DesktopMixedTransitionHandler
+
@Before
fun setUp() {
+ whenever(userRepositories.current).thenReturn(desktopRepository)
+ whenever(userRepositories.getProfile(Mockito.anyInt())).thenReturn(desktopRepository)
mixedHandler =
DesktopMixedTransitionHandler(
context,
transitions,
- desktopRepository,
+ userRepositories,
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController,
@@ -146,7 +155,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
whenever(freeformTaskTransitionHandler.startRemoveTransition(wct))
@@ -158,7 +169,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_startsCloseTransition() {
val wct = WindowContainerTransaction()
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -178,7 +191,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
fun startAnimation_withoutClosingDesktopTask_returnsFalse() {
val transition = mock<IBinder>()
val transitionInfo =
- createTransitionInfo(
+ createCloseTransitionInfo(
changeMode = TRANSIT_OPEN,
task = createTask(WINDOWING_MODE_FREEFORM)
)
@@ -197,12 +210,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
- val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+ val transitionInfo = createCloseTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
whenever(
closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
)
@@ -225,12 +239,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
- val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
- whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(1)
+ val transitionInfo = createCloseTransitionInfo(
+ task = createTask(WINDOWING_MODE_FREEFORM), withWallpaper = true)
whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
.thenReturn(mock())
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -266,7 +282,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@DisableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -302,7 +319,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -338,7 +357,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange, otherChange)
),
@@ -378,7 +397,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val immersiveChange = createChange(immersiveTask)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange, immersiveChange)
),
@@ -401,7 +420,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -418,7 +439,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange)
),
@@ -431,7 +452,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -450,7 +473,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange, minimizeChange)
),
@@ -463,7 +486,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -482,7 +507,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val started = mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(nonLaunchTaskChange)
),
@@ -512,7 +537,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val started = mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(createChange(task, mode = TRANSIT_OPEN))
),
@@ -546,7 +571,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val openingChange = createChange(openingTask, mode = TRANSIT_OPEN)
val started = mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(immersiveChange, openingChange)
),
@@ -560,7 +585,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -579,7 +606,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange)
),
@@ -592,7 +619,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -613,7 +642,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange, minimizeChange)
),
@@ -643,7 +672,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val launchTaskChange = createChange(launchingTask)
mixedHandler.startAnimation(
transition,
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_OPEN,
listOf(launchTaskChange)
),
@@ -700,7 +729,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val started = mixedHandler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_TO_BACK,
listOf(minimizingTaskChange)
),
@@ -739,7 +768,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
mixedHandler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
+ createCloseTransitionInfo(
TRANSIT_TO_BACK,
listOf(minimizingTaskChange)
),
@@ -759,12 +788,12 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
}
- private fun createTransitionInfo(
- type: Int = WindowManager.TRANSIT_CLOSE,
+ private fun createCloseTransitionInfo(
changeMode: Int = WindowManager.TRANSIT_CLOSE,
- task: RunningTaskInfo
+ task: RunningTaskInfo,
+ withWallpaper: Boolean = false,
): TransitionInfo =
- TransitionInfo(type, 0 /* flags */).apply {
+ TransitionInfo(WindowManager.TRANSIT_CLOSE, 0 /* flags */).apply {
addChange(
TransitionInfo.Change(mock(), closingTaskLeash).apply {
mode = changeMode
@@ -772,9 +801,18 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
taskInfo = task
}
)
+ if (withWallpaper) {
+ addChange(
+ TransitionInfo.Change(/* container= */ mock(), /* leash= */ mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ parent = null
+ taskInfo = createWallpaperTask()
+ }
+ )
+ }
}
- private fun createTransitionInfo(
+ private fun createCloseTransitionInfo(
@TransitionType type: Int,
changes: List<TransitionInfo.Change> = emptyList()
): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply {
@@ -795,4 +833,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(windowingMode)
.build()
+
+ private fun createWallpaperTask() =
+ RunningTaskInfo().apply {
+ token = WindowContainerToken(mock<IWindowContainerToken>())
+ baseIntent =
+ Intent().apply {
+ component = DesktopWallpaperActivity.wallpaperActivityComponent
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index d4682c1325f2..e57ae2a86859 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -53,10 +53,10 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
@@ -91,7 +91,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
private val focusTransitionObserver = mock<FocusTransitionObserver>()
- private val testExecutor = mock<ShellExecutor>()
+ private val testExecutor = TestShellExecutor()
private val inputManager = mock<InputManager>()
private val displayController = mock<DisplayController>()
private val displayLayout = mock<DisplayLayout>()
@@ -137,8 +137,13 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver, testExecutor
+ Optional.of(desktopModeWindowDecorViewModel),
+ Optional.of(desktopTasksController),
+ inputManager,
+ shellTaskOrganizer,
+ focusTransitionObserver,
+ testExecutor,
+ displayController
)
}
@@ -148,6 +153,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
runningTasks.clear()
testScope.cancel()
+ testExecutor.flushAll()
}
@Test
@@ -177,6 +183,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
.build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ testExecutor.flushAll()
assertThat(result).isTrue()
verify(desktopTasksController).moveToNextDisplay(task.taskId)
@@ -199,6 +206,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setModifierState(KeyEvent.META_META_ON)
.build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ testExecutor.flushAll()
assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel).onSnapResize(
@@ -226,6 +234,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setModifierState(KeyEvent.META_META_ON)
.build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ testExecutor.flushAll()
assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel).onSnapResize(
@@ -253,12 +262,17 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setModifierState(KeyEvent.META_META_ON)
.build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ testExecutor.flushAll()
assertThat(result).isTrue()
verify(desktopTasksController).toggleDesktopTaskSize(
task,
- ResizeTrigger.MAXIMIZE_MENU,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ ToggleTaskSizeInteraction(
+ isMaximized = isTaskMaximized(task, displayController),
+ source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+ inputMethod =
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ ),
)
}
@@ -279,8 +293,10 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setModifierState(KeyEvent.META_META_ON)
.build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ testExecutor.flushAll()
assertThat(result).isTrue()
+ verify(desktopTasksController).minimizeTask(task)
}
private fun setUpFreeformTask(
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 7f790d574a7e..344140d91ab3 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
@@ -30,7 +30,6 @@ import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
@@ -70,7 +69,6 @@ class DesktopRepositoryTest : ShellTestCase() {
@Mock private lateinit var testExecutor: ShellExecutor
@Mock private lateinit var persistentRepository: DesktopPersistentRepository
- @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Before
fun setUp() {
@@ -80,11 +78,9 @@ class DesktopRepositoryTest : ShellTestCase() {
repo =
DesktopRepository(
- context,
- shellInit,
persistentRepository,
- repositoryInitializer,
- datastoreScope
+ datastoreScope,
+ DEFAULT_USER_ID
)
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
@@ -153,6 +149,14 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
+ fun addTask_multipleDisplays_moveToAnotherDisplay() {
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
+ assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty()
+ assertThat(repo.getFreeformTasksInZOrder(SECOND_DISPLAY)).containsExactly(1)
+ }
+
+ @Test
fun removeActiveTask_notifiesActiveTaskListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
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 8e323acc4e66..b4daa6637f83 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
@@ -22,6 +22,7 @@ import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
@@ -29,6 +30,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -47,131 +49,163 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
+ private val desktopUserRepositories = mock<DesktopUserRepositories>()
private val desktopRepository = mock<DesktopRepository>()
@Before
fun setUp() {
- desktopTaskChangeListener = DesktopTaskChangeListener(desktopRepository)
+ desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories)
+
+ whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
+ whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository)
}
@Test
fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(false)
desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible)
- verify(desktopRepository, never()).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current, never())
+ .addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current, never())
+ .removeFreeformTask(task.displayId, task.taskId)
}
@Test
fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
}
@Test
fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(false)
desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current)
+ .addTask(task.displayId, task.taskId, task.isVisible)
}
@Test
fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current)
+ .addTask(task.displayId, task.taskId, task.isVisible)
}
@Test
fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskChanging(task)
- verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current)
+ .removeFreeformTask(task.displayId, task.taskId)
}
@Test
fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskChanging(task)
- verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, task.isVisible)
}
@Test
fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskChanging(task)
- verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, task.isVisible)
}
@Test
fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskMovingToFront(task)
- verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current)
+ .removeFreeformTask(task.displayId, task.taskId)
}
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
- whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(false)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+ .thenReturn(false)
desktopTaskChangeListener.onTaskClosing(task)
- verify(desktopRepository).updateTask(task.displayId, task.taskId, isVisible = false)
- verify(desktopRepository).minimizeTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, isVisible = false)
+ verify(desktopUserRepositories.current)
+ .minimizeTask(task.displayId, task.taskId)
}
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
- whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskClosing(task)
- verify(desktopRepository, never()).minimizeTask(task.displayId, task.taskId)
- verify(desktopRepository).removeClosingTask(task.taskId)
- verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current, never())
+ .minimizeTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current)
+ .removeClosingTask(task.taskId)
+ verify(desktopUserRepositories.current)
+ .removeFreeformTask(task.displayId, task.taskId)
}
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
- whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+ .thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+ .thenReturn(true)
desktopTaskChangeListener.onTaskClosing(task)
- verify(desktopRepository).removeClosingTask(task.taskId)
- verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current).removeClosingTask(task.taskId)
+ verify(desktopUserRepositories.current)
+ .removeFreeformTask(task.displayId, task.taskId)
}
}
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 5c0027220ec9..7c9494ce7026 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
@@ -21,6 +21,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.KeyguardManager
import android.app.PendingIntent
+import android.app.PictureInPictureParams
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -44,6 +45,7 @@ import android.os.Binder
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -93,6 +95,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -233,9 +236,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock private lateinit var resources: Resources
@Mock
lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
+ @Mock private lateinit var userManager: UserManager
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
private lateinit var taskRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
@@ -267,12 +272,18 @@ class DesktopTasksControllerTest : ShellTestCase() {
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository =
- DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope)
+ userRepositories =
+ DesktopUserRepositories(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ testScope,
+ userManager)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
- taskRepository,
+ userRepositories,
shellTaskOrganizer,
MAX_TASK_LIMIT,
mockInteractionJankMonitor,
@@ -315,6 +326,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ taskRepository = userRepositories.current
}
private fun createController(): DesktopTasksController {
@@ -338,7 +351,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
mMockDesktopImmersiveController,
- taskRepository,
+ userRepositories,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
@@ -377,7 +390,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task1 = setUpFreeformTask()
val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
@@ -399,21 +419,29 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() {
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH
+ )
+ )
verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task1,
- 0,
- 0,
- displayController
+ eq(ResizeTrigger.MAXIMIZE_BUTTON),
+ eq(InputMethod.TOUCH),
+ eq(task1),
+ anyOrNull(),
+ anyOrNull(),
+ eq(displayController),
+ anyOrNull()
)
assertThat(argumentCaptor.value).isFalse()
}
@@ -1124,7 +1152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -1140,7 +1168,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -1697,6 +1725,34 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val handler = mock(TransitionHandler::class.java)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any()))
+ .thenReturn(Binder())
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction())
+ )
+
+ controller.minimizeTask(task)
+
+ verify(freeformTaskTransitionStarter).startPipTransition(any())
+ verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() {
+ val task = setUpPipTask(autoEnterEnabled = false)
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(Binder())
+
+ controller.minimizeTask(task)
+
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
+ verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
+ }
+
+ @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -2233,7 +2289,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -2251,7 +2307,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task =
setUpFreeformTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -3006,20 +3062,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
.thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
// Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
spyController.onDragPositioningEnd(
task,
mockSurface,
Point(100, 50), /* position */
PointF(200f, 300f), /* inputCoordinate */
- Rect(100, 50, 500, 1000), /* currentDragBounds */
+ currentDragBounds,
Rect(0, 50, 2000, 2000) /* validDragArea */,
Rect() /* dragStartBounds */,
motionEvent,
desktopWindowDecoration)
// Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct()
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
// Assert event is properly logged
verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
@@ -3278,44 +3335,41 @@ class DesktopTasksControllerTest : ShellTestCase() {
setUpLandscapeDisplay()
val task = setUpFreeformTask()
val taskToRequest = setUpFreeformTask()
- val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
runOpenInstance(task, taskToRequest.taskId)
- verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
- .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(),
+ anyOrNull(), anyOrNull())
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, taskToRequest)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
fun openInstance_fromFreeform_minimizesIfNeeded() {
setUpLandscapeDisplay()
- val homeTask = setUpHomeTask()
val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
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 = getLatestWct(type = TRANSIT_OPEN)
- // Home is moved to front of everything.
- assertThat(
- wct.hierarchyOps.any { hop ->
- hop.container == homeTask.token.asBinder() && hop.toTop
- }
- ).isTrue()
- // And the oldest task isn't moved in front of home, effectively minimizing it.
- assertThat(
- wct.hierarchyOps.none { hop ->
- hop.container == oldestTask.token.asBinder() && hop.toTop
- }
- ).isTrue()
+ val wct = wctCaptor.firstValue
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[1], toTop = true)
+ wct.assertReorderAt(1, oldestTask, toTop = false)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
setUpLandscapeDisplay()
- val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val immersiveTask = setUpFreeformTask()
taskRepository.setTaskInFullImmersiveState(
@@ -3325,11 +3379,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
val runOnStartTransit = RunOnStartTransitionCallback()
val transition = Binder()
- whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull()))
+ whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(),
+ anyOrNull(), anyOrNull()
+ ))
.thenReturn(transition)
whenever(mMockDesktopImmersiveController
.exitImmersiveIfApplicable(
- any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()))
+ any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any()))
.thenReturn(
ExitResult.Exit(
exitingTask = immersiveTask.taskId,
@@ -3359,7 +3415,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3584,7 +3647,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3604,7 +3674,15 @@ class DesktopTasksControllerTest : ShellTestCase() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
+
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
verify(desktopModeEventLogger, never()).logTaskResizingEnded(
any(), any(), any(), any(),
@@ -3618,11 +3696,25 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
// Maximize
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
// Restore
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH
+ )
+ )
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3645,12 +3737,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
// Maximize
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
// Restore
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH
+ )
+ )
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3673,12 +3779,26 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
// Maximize
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
// Restore
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH
+ )
+ )
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
@@ -3699,11 +3819,25 @@ class DesktopTasksControllerTest : ShellTestCase() {
val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
// Maximize
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH
+ )
+ )
task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
// Restore
- controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH)
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH
+ )
+ )
// Assert last bounds before maximize removed after use
assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
@@ -4124,6 +4258,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
return task
}
+ private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo {
+ return setUpFreeformTask().apply {
+ pictureInPictureParams = PictureInPictureParams.Builder()
+ .setAutoEnterEnabled(autoEnterEnabled)
+ .build()
+ }
+ }
+
private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createHomeTask(displayId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
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 797b12505ef2..0712d58166bb 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
@@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import android.os.Binder
import android.os.Handler
+import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -73,7 +74,6 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.quality.Strictness
-
/**
* Test class for {@link DesktopTasksLimiter}
*
@@ -95,9 +95,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var persistentRepository: DesktopPersistentRepository
@Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ @Mock lateinit var userManager: UserManager
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
+ private lateinit var userRepositories: DesktopUserRepositories
private lateinit var desktopTaskRepo: DesktopRepository
private lateinit var shellInit: ShellInit
private lateinit var testScope: CoroutineScope
@@ -111,16 +113,18 @@ class DesktopTasksLimiterTest : ShellTestCase() {
Dispatchers.setMain(StandardTestDispatcher())
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
- desktopTaskRepo =
- DesktopRepository(
+ userRepositories =
+ DesktopUserRepositories(
context,
shellInit,
persistentRepository,
repositoryInitializer,
- testScope
+ testScope,
+ userManager
)
+ desktopTaskRepo = userRepositories.current
desktopTasksLimiter =
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
}
@@ -133,7 +137,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0,
interactionJankMonitor, mContext, handler)
}
}
@@ -141,7 +145,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5,
interactionJankMonitor, mContext, handler)
}
}
@@ -411,7 +415,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
desktopTasksLimiter =
- DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2,
+ DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2,
interactionJankMonitor, mContext, handler)
val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
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 7f1c1db3207a..b31a3f5fa642 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
@@ -39,6 +39,7 @@ import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -50,6 +51,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isA
import org.mockito.Mockito
@@ -66,17 +68,17 @@ class DesktopTasksTransitionObserverTest {
@JvmField
@Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeStatus::class.java)
- .build()!!
+ ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
private val testExecutor = mock<ShellExecutor>()
private val mockShellInit = mock<ShellInit>()
private val transitions = mock<Transitions>()
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val userRepositories = mock<DesktopUserRepositories>()
private val taskRepository = mock<DesktopRepository>()
private val mixedHandler = mock<DesktopMixedTransitionHandler>()
+ private val backAnimationController = mock<BackAnimationController>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
@@ -86,9 +88,18 @@ class DesktopTasksTransitionObserverTest {
whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
shellInit = spy(ShellInit(testExecutor))
+ whenever(userRepositories.current).thenReturn(taskRepository)
+ whenever(userRepositories.getProfile(anyInt())).thenReturn(taskRepository)
+
transitionObserver =
DesktopTasksTransitionObserver(
- context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit
+ context,
+ userRepositories,
+ transitions,
+ shellTaskOrganizer,
+ mixedHandler,
+ backAnimationController,
+ shellInit
)
}
@@ -100,8 +111,7 @@ class DesktopTasksTransitionObserverTest {
transitionObserver.onTransitionReady(
transition = mock(),
- info =
- createBackNavigationTransition(task),
+ info = createBackNavigationTransition(task),
startTransaction = mock(),
finishTransaction = mock(),
)
@@ -112,14 +122,59 @@ class DesktopTasksTransitionObserverTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun backNavigation_withCloseTransitionNotLastTask_taskMinimized() {
+ val task = createTaskInfo(1)
+ val transition = mock<IBinder>()
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(2)
+ whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
+ whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = createBackNavigationTransition(task, TRANSIT_CLOSE),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ val pendingTransition =
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, task.taskId, isLastTask = false)
+ verify(mixedHandler).addPendingMixedTransition(pendingTransition)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun backNavigation_withCloseTransitionLastTask_taskMinimized() {
+ val task = createTaskInfo(1)
+ val transition = mock<IBinder>()
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+ whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
+ whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = createBackNavigationTransition(task, TRANSIT_CLOSE, true),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ val pendingTransition =
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, task.taskId, isLastTask = true)
+ verify(mixedHandler).addPendingMixedTransition(pendingTransition)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun backNavigation_nullTaskInfo_taskNotMinimized() {
val task = createTaskInfo(1)
whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
transitionObserver.onTransitionReady(
transition = mock(),
- info =
- createBackNavigationTransition(null),
+ info = createBackNavigationTransition(null),
startTransaction = mock(),
finishTransaction = mock(),
)
@@ -168,7 +223,7 @@ class DesktopTasksTransitionObserverTest {
val mockTransition = Mockito.mock(IBinder::class.java)
val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
val wallpaperToken = MockToken().token()
- whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1)
+ whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(0)
whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken)
transitionObserver.onTransitionReady(
@@ -185,17 +240,27 @@ class DesktopTasksTransitionObserverTest {
}
private fun createBackNavigationTransition(
- task: RunningTaskInfo?
+ task: RunningTaskInfo?,
+ type: Int = TRANSIT_TO_BACK,
+ withWallpaper: Boolean = false,
): TransitionInfo {
- return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
+ return TransitionInfo(type, 0 /* flags */).apply {
addChange(
Change(mock(), mock()).apply {
- mode = TRANSIT_TO_BACK
+ mode = type
parent = null
taskInfo = task
flags = flags
- }
- )
+ })
+ if (withWallpaper) {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_CLOSE
+ parent = null
+ taskInfo = createWallpaperTaskInfo()
+ flags = flags
+ })
+ }
}
}
@@ -210,14 +275,11 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = task
flags = flags
- }
- )
+ })
}
}
- private fun createCloseTransition(
- task: RunningTaskInfo?
- ): TransitionInfo {
+ private fun createCloseTransition(task: RunningTaskInfo?): TransitionInfo {
return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply {
addChange(
Change(mock(), mock()).apply {
@@ -225,8 +287,7 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = task
flags = flags
- }
- )
+ })
}
}
@@ -238,8 +299,7 @@ class DesktopTasksTransitionObserverTest {
if (handlerClass == null) {
Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull())
} else {
- Mockito.verify(transitions)
- .startTransition(eq(type), arg.capture(), isA(handlerClass))
+ Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
}
return arg.value
}
@@ -263,8 +323,15 @@ class DesktopTasksTransitionObserverTest {
displayId = DEFAULT_DISPLAY
configuration.windowConfiguration.windowingMode = windowingMode
token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
- baseIntent = Intent().apply {
- component = ComponentName("package", "component.name")
- }
+ baseIntent = Intent().apply { component = ComponentName("package", "component.name") }
+ }
+
+ private fun createWallpaperTaskInfo() =
+ RunningTaskInfo().apply {
+ token = mock<WindowContainerToken>()
+ baseIntent =
+ Intent().apply {
+ component = DesktopWallpaperActivity.wallpaperActivityComponent
+ }
}
}
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 866d1b3880b0..aee8821a63f6 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
@@ -73,10 +73,10 @@ object DesktopTestHelpers {
.setLastActiveTime(100)
.build()
- /** Create a new System Modal task, i.e. a task with a single transparent activity. */
+ /** Create a new System Modal task, i.e. a task with only transparent activities. */
fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
createFullscreenTaskBuilder(displayId)
- .setTopActivityTransparent(true)
+ .setActivityStackTransparent(true)
.setNumActivities(1)
.build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
new file mode 100644
index 000000000000..5767df4c5a8e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+class DesktopUserRepositoriesTest : ShellTestCase() {
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var userRepositories: DesktopUserRepositories
+ private lateinit var shellInit: ShellInit
+ private lateinit var datastoreScope: CoroutineScope
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val persistentRepository = mock<DesktopPersistentRepository>()
+ private val repositoryInitializer = mock<DesktopRepositoryInitializer>()
+ private val userManager = mock<UserManager>()
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(ActivityManager::class.java)
+ .startMocking()
+ doReturn(USER_ID_1).`when` { ActivityManager.getCurrentUser() }
+
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ shellInit = spy(ShellInit(testExecutor))
+
+ val profiles: MutableList<UserInfo> = mutableListOf(
+ UserInfo(USER_ID_1, "User 1", 0),
+ UserInfo(PROFILE_ID_2, "Profile 2", 0))
+ whenever(userManager.getProfiles(USER_ID_1)).thenReturn(profiles)
+
+ userRepositories = DesktopUserRepositories(
+ context, shellInit, persistentRepository, repositoryInitializer, datastoreScope,
+ userManager)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ datastoreScope.cancel()
+ }
+
+ @Test
+ fun getCurrent_returnsUserId() {
+ val desktopRepository: DesktopRepository = userRepositories.current
+
+ assertThat(desktopRepository.userId).isEqualTo(USER_ID_1)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun getProfile_flagEnabled_returnsProfileGroupId() {
+ val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2)
+
+ assertThat(desktopRepository.userId).isEqualTo(USER_ID_1)
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun getProfile_flagDisabled_returnsProfileId() {
+ val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2)
+
+ assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
+ }
+
+ private companion object {
+ const val USER_ID_1 = 7
+ const val PROFILE_ID_2 = 5
+ }
+}
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 79e16fea272d..13528b947609 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
@@ -607,7 +607,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator)
return token
}
@@ -661,6 +661,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
return TestRunningTaskInfoBuilder()
.setActivityType(if (isHome) ACTIVITY_TYPE_HOME else ACTIVITY_TYPE_STANDARD)
.setWindowingMode(windowingMode)
+ .setUserId(mContext.userId)
.build()
.also {
whenever(splitScreenController.isTaskInSplitScreen(it.taskId))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index 226e974d2875..c33005e7cfcc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -25,10 +25,11 @@ import android.view.WindowManager.TRANSIT_OPEN
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
@@ -42,6 +43,10 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Tests for {@link SystemModalsTransitionHandler}
+ * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class SystemModalsTransitionHandlerTest : ShellTestCase() {
@@ -49,6 +54,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
private val animExecutor = mock<ShellExecutor>()
private val shellInit = mock<ShellInit>()
private val transitions = mock<Transitions>()
+ private val desktopUserRepositories = mock<DesktopUserRepositories>()
private val desktopRepository = mock<DesktopRepository>()
private val startT = mock<SurfaceControl.Transaction>()
private val finishT = mock<SurfaceControl.Transaction>()
@@ -58,6 +64,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
@Before
fun setUp() {
// Simulate having one Desktop task so that we see Desktop Mode as active
+ whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
transitionHandler = createTransitionHandler()
}
@@ -69,7 +76,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
animExecutor,
shellInit,
transitions,
- desktopRepository,
+ desktopUserRepositories,
)
@Test
@@ -79,7 +86,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
@Test
fun startAnimation_desktopNotActive_doesNotAnimate() {
- whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
+ whenever(desktopUserRepositories.current.getVisibleTaskCount(anyInt())).thenReturn(1)
val info =
TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN, createSystemModalTask())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
index 8495580f42a5..4f7e80cf8330 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -112,7 +112,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
datastoreRepository.addOrUpdateDesktop(
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
- freeformTasksInZOrder = freeformTasksInZOrder)
+ freeformTasksInZOrder = freeformTasksInZOrder,
+ userId = DEFAULT_USER_ID)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2)
@@ -135,7 +136,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
datastoreRepository.addOrUpdateDesktop(
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
- freeformTasksInZOrder = freeformTasksInZOrder)
+ freeformTasksInZOrder = freeformTasksInZOrder,
+ userId = DEFAULT_USER_ID)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState)
@@ -158,7 +160,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
datastoreRepository.addOrUpdateDesktop(
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
- freeformTasksInZOrder = freeformTasksInZOrder)
+ freeformTasksInZOrder = freeformTasksInZOrder,
+ userId = DEFAULT_USER_ID)
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty()
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 975342902814..1c88a290d677 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
@@ -16,14 +16,17 @@
package com.android.wm.shell.desktopmode.persistence
+import android.os.UserManager
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -36,26 +39,30 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.inOrder
import org.mockito.Mockito.spy
-import org.mockito.kotlin.any
import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
class DesktopRepositoryInitializerTest : ShellTestCase() {
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
+
private lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var shellInit: ShellInit
private lateinit var datastoreScope: CoroutineScope
- private lateinit var desktopRepository: DesktopRepository
+ private lateinit var desktopUserRepositories: DesktopUserRepositories
private val persistentRepository = mock<DesktopPersistentRepository>()
+ private val userManager = mock<UserManager>()
private val testExecutor = mock<ShellExecutor>()
@Before
@@ -65,55 +72,193 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
repositoryInitializer =
DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope)
- desktopRepository =
- DesktopRepository(
- context, shellInit, persistentRepository, repositoryInitializer, datastoreScope)
+ desktopUserRepositories =
+ DesktopUserRepositories(
+ context, shellInit, persistentRepository, repositoryInitializer, datastoreScope,
+ userManager
+ )
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+ fun initWithPersistence_multipleUsers_addedCorrectly() =
+ runTest(StandardTestDispatcher()) {
+ whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn(
+ mapOf(
+ USER_ID_1 to desktopRepositoryState1,
+ USER_ID_2 to desktopRepositoryState2
+ )
+ )
+ whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
+ .thenReturn(desktopRepositoryState1)
+ whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2))
+ .thenReturn(desktopRepositoryState2)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1))
+ .thenReturn(desktop1)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2))
+ .thenReturn(desktop2)
+ whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3))
+ .thenReturn(desktop3)
+
+ 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)
+ )
+ .containsExactly(1, 3, 4, 5)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
+ .containsExactly(5, 1)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getMinimizedTasks(DEFAULT_DISPLAY)
+ )
+ .containsExactly(3, 4)
+ .inOrder()
+
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_2)
+ .getActiveTasks(DEFAULT_DISPLAY)
+ )
+ .containsExactly(7, 8)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_2)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
+ .contains(7)
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_2)
+ .getMinimizedTasks(DEFAULT_DISPLAY)
+ ).containsExactly(8)
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
- fun initWithPersistence_multipleTasks_addedCorrectly() =
+ fun initWithPersistence_singleUser_addedCorrectly() =
runTest(StandardTestDispatcher()) {
- val freeformTasksInZOrder = listOf(1, 2, 3)
- whenever(persistentRepository.readDesktop(any(), any()))
- .thenReturn(
- Desktop.newBuilder()
- .setDesktopId(1)
- .addAllZOrderedTasks(freeformTasksInZOrder)
- .putTasksByTaskId(
- 1,
- DesktopTask.newBuilder()
- .setTaskId(1)
- .setDesktopTaskState(DesktopTaskState.VISIBLE)
- .build())
- .putTasksByTaskId(
- 2,
- DesktopTask.newBuilder()
- .setTaskId(2)
- .setDesktopTaskState(DesktopTaskState.VISIBLE)
- .build())
- .putTasksByTaskId(
- 3,
- DesktopTask.newBuilder()
- .setTaskId(3)
- .setDesktopTaskState(DesktopTaskState.MINIMIZED)
- .build())
- .build())
-
- repositoryInitializer.initialize(desktopRepository)
-
- verify(persistentRepository).readDesktop(any(), any())
- assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY))
- .containsExactly(1, 2, 3)
+ 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)
+
+ // 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)
+ )
+ .containsExactly(1, 3, 4, 5)
+ .inOrder()
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
+ .containsExactly(5, 1)
.inOrder()
- assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY))
- .containsExactly(1, 2)
+ assertThat(
+ desktopUserRepositories.getProfile(USER_ID_1)
+ .getMinimizedTasks(DEFAULT_DISPLAY)
+ )
+ .containsExactly(3, 4)
.inOrder()
- assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3)
}
@After
fun tearDown() {
datastoreScope.cancel()
}
+
+ private companion object {
+ const val USER_ID_1 = 5
+ const val USER_ID_2 = 6
+ const val DESKTOP_ID_1 = 2
+ const val DESKTOP_ID_2 = 3
+ const val DESKTOP_ID_3 = 4
+
+ val freeformTasksInZOrder1 = listOf(1, 3)
+ val desktop1: Desktop = Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_1)
+ .addAllZOrderedTasks(freeformTasksInZOrder1)
+ .putTasksByTaskId(
+ 1,
+ DesktopTask.newBuilder()
+ .setTaskId(1)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build()
+ )
+ .putTasksByTaskId(
+ 3,
+ DesktopTask.newBuilder()
+ .setTaskId(3)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build()
+ )
+ .build()
+
+ val freeformTasksInZOrder2 = listOf(4, 5)
+ val desktop2: Desktop = Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_2)
+ .addAllZOrderedTasks(freeformTasksInZOrder2)
+ .putTasksByTaskId(
+ 4,
+ DesktopTask.newBuilder()
+ .setTaskId(4)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build()
+ )
+ .putTasksByTaskId(
+ 5,
+ DesktopTask.newBuilder()
+ .setTaskId(5)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build()
+ )
+ .build()
+
+ val freeformTasksInZOrder3 = listOf(7, 8)
+ val desktop3: Desktop = Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_3)
+ .addAllZOrderedTasks(freeformTasksInZOrder3)
+ .putTasksByTaskId(
+ 7,
+ DesktopTask.newBuilder()
+ .setTaskId(7)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build()
+ )
+ .putTasksByTaskId(
+ 8,
+ DesktopTask.newBuilder()
+ .setTaskId(8)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build()
+ )
+ .build()
+ val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
+ .putDesktop(DESKTOP_ID_1, desktop1)
+ .putDesktop(DESKTOP_ID_2, desktop2)
+ .build()
+ val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
+ .putDesktop(DESKTOP_ID_3, desktop3)
+ .build()
+ }
}
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 b504a88e71fc..b8629b2f00d2 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
@@ -27,6 +27,7 @@ import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HA
import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,6 +48,7 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -81,6 +83,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Mock
private SurfaceControl mMockSurfaceControl;
@Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
private DesktopRepository mDesktopRepository;
@Mock
private DesktopTasksController mDesktopTasksController;
@@ -101,13 +105,14 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
.mockStatic(DesktopModeStatus.class)
.startMocking();
doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
-
+ when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+ when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
mFreeformTaskListener =
new FreeformTaskListener(
mContext,
mShellInit,
mTaskOrganizer,
- Optional.of(mDesktopRepository),
+ Optional.of(mDesktopUserRepositories),
Optional.of(mDesktopTasksController),
mLaunchAdjacentController,
mWindowDecorViewModel,
@@ -123,7 +128,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible = true);
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible = true);
}
@Test
@@ -135,7 +141,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible);
}
@Test
@@ -147,7 +154,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- verify(mDesktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .addTask(task.displayId, task.taskId, task.isVisible);
}
@Test
@@ -158,7 +166,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible);
}
@Test
@@ -171,7 +180,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopRepository, never())
+ verify(mDesktopUserRepositories.getCurrent(), never())
.addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible);
}
@@ -203,10 +212,11 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
@DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
- public void onTaskVanished_nonClosingTask_noTransitionObservers_isMinimized() {
+ public void onTaskVanished_minimizedTask_noTransitionObservers_isNotRemoved() {
ActivityManager.RunningTaskInfo task =
new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isVisible = true;
+ when(mDesktopRepository.isMinimizedTask(task.taskId)).thenReturn(true);
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
@@ -214,7 +224,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never()).removeFreeformTask(task.displayId,
+ task.taskId);
}
@Test
@@ -227,14 +238,17 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
- when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent()
+ .isClosingTask(task.taskId)).thenReturn(true);
task.isVisible = false;
task.displayId = INVALID_DISPLAY;
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
- verify(mDesktopRepository).removeClosingTask(task.taskId);
- verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent()).removeClosingTask(task.taskId);
+ verify(mDesktopUserRepositories.getCurrent())
+ .removeFreeformTask(task.displayId, task.taskId);
}
@Test
@@ -246,9 +260,12 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskVanished(task);
- verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
- verify(mDesktopRepository, never()).removeClosingTask(task.taskId);
- verify(mDesktopRepository, never()).removeFreeformTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .removeClosingTask(task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .removeFreeformTask(task.displayId, task.taskId);
}
@Test
@@ -274,7 +291,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskInfoChanged(task);
verify(mTaskChangeListener, never()).onTaskChanging(any());
- verify(mDesktopRepository).updateTask(task.displayId, task.taskId, task.isVisible);
+ verify(mDesktopUserRepositories.getCurrent())
+ .updateTask(task.displayId, task.taskId, task.isVisible);
}
@Test
@@ -289,7 +307,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
mFreeformTaskListener.onTaskInfoChanged(task);
verify(mTaskChangeListener).onNonTransitionTaskChanging(any());
- verify(mDesktopRepository, never())
+ verify(mDesktopUserRepositories.getCurrent(), never())
.updateTask(task.displayId, task.taskId, task.isVisible);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 7d063a0a773f..256ed413c2cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -48,7 +48,6 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -179,7 +178,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Test
public void testControllerRegisteresExternalInterface() {
verify(mMockShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ eq(IOneHanded.DESCRIPTOR), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 289fd2d838fd..2eb2c3b8e2f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -65,7 +65,7 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,7 +103,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
- @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+ @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories;
@Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@@ -136,7 +136,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
Optional.empty() /* pipPerfHintControllerOptional */,
- mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+ mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer,
mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
mMainExecutor);
mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index b123f4dfac9e..5ef934ce8394 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -58,6 +58,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -71,7 +72,6 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -178,7 +178,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registerExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
+ eq(IPip.DESCRIPTOR), any(), eq(mPipController));
}
@Test
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 cab625216236..3fe8c109807a 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
@@ -42,8 +42,10 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -56,6 +58,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Unit test against {@link PipScheduler}
*/
@@ -79,6 +83,8 @@ public class PipSchedulerTest {
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
+ @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -96,7 +102,8 @@ public class PipSchedulerTest {
.thenReturn(mMockTransaction);
mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
- mMockPipTransitionState);
+ mMockPipTransitionState, mMockOptionalDesktopUserRepositories,
+ mRootTaskDisplayAreaOrganizer);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 68c8aab8849d..22b45e8c63af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL;
+import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
@@ -74,9 +74,9 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.shared.GroupedTaskInfo;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -113,8 +113,6 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopRepository mDesktopRepository;
- @Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
private DisplayInsetsController mDisplayInsetsController;
@@ -122,6 +120,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
private IRecentTasksListener mRecentTasksListener;
@Mock
private TaskStackTransitionObserver mTaskStackTransitionObserver;
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -142,6 +144,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
.when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
mMainExecutor = new TestShellExecutor();
+ when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+ when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getSystemService(KeyguardManager.class))
.thenReturn(mock(KeyguardManager.class));
@@ -150,7 +154,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -178,7 +182,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void instantiateController_addExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ eq(IRecentTasks.DESCRIPTOR), any(), any());
+ }
+
+ @Test
+ public void instantiateController_initializesRepository() {
+ verify(mDesktopUserRepositories, times(1)).getCurrent();
+ verify(mDesktopRepository, times(1)).addActiveTaskListener(any());
}
@Test
@@ -240,7 +250,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
t3.taskId, -1);
}
- @EnableFlags(FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
@Test
public void testGetRecentTasks_removesDesktopWallpaperActivity() {
RecentTaskInfo t1 = makeTaskInfo(1);
@@ -323,8 +333,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -362,8 +372,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
- when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(5)).thenReturn(true);
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -402,8 +412,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -431,7 +441,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(false);
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(4)).thenReturn(false);
when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
@@ -470,8 +482,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
setRawList(t1, t2);
- when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(2)).thenReturn(true);
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index f0f5fe159069..894d238b7e15 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -100,7 +101,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopRepository mDesktopRepository;
+ private DesktopUserRepositories mDesktopUserRepositories;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -112,6 +113,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
@Mock
private Transitions mTransitions;
+ @Mock private DesktopRepository mDesktopRepository;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -131,6 +134,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
ExtendedMockito.doReturn(true)
.when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getSystemService(KeyguardManager.class))
@@ -140,7 +144,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase {
mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
+ Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver,
mMainExecutor);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 72a7a3f5ec99..bb9703fce2e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -71,10 +71,10 @@ import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -119,6 +119,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock WindowDecorViewModel mWindowDecorViewModel;
@Mock DesktopTasksController mDesktopTasksController;
@Mock MultiInstanceHelper mMultiInstanceHelper;
+ @Mock SplitState mSplitState;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -136,7 +137,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
- mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
+ mStageCoordinator, mMultiInstanceHelper, mSplitState, mMainExecutor, mMainHandler));
}
@Test
@@ -178,7 +179,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
mSplitScreenController.onInit();
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ eq(ISplitScreen.DESCRIPTOR), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index d13a8888edc7..1a2d60ddad3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -35,6 +35,7 @@ import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.transition.Transitions;
@@ -80,11 +81,11 @@ public class SplitTestUtils {
ShellExecutor mainExecutor, Handler mainHandler,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
- launchAdjacentController, windowDecorViewModel);
+ launchAdjacentController, windowDecorViewModel, splitState);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e32cf3899a03..de77837cb0e5 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
@@ -78,6 +78,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.TestRemoteTransition;
@@ -108,6 +109,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private Transitions mTransitions;
@Mock private IconProvider mIconProvider;
@Mock private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock private SplitState mSplitState;
@Mock private ShellExecutor mMainExecutor;
@Mock private Handler mMainHandler;
@Mock private LaunchAdjacentController mLaunchAdjacentController;
@@ -144,7 +146,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
- mLaunchAdjacentController, Optional.empty());
+ mLaunchAdjacentController, Optional.empty(), mSplitState);
mStageCoordinator.setMixedHandler(mMixedHandler);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
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 1e739cd446ae..7afcce1243e3 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
@@ -71,6 +71,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
@@ -116,6 +117,8 @@ public class StageCoordinatorTests extends ShellTestCase {
private LaunchAdjacentController mLaunchAdjacentController;
@Mock
private DefaultMixedHandler mDefaultMixedHandler;
+ @Mock
+ private SplitState mSplitState;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -139,7 +142,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
- Optional.empty()));
+ Optional.empty(), mSplitState));
mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build();
when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 7fd1c11e61ae..17a5f5c0f3d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -42,7 +42,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -99,7 +98,7 @@ public class StartingWindowControllerTests extends ShellTestCase {
@Test
public void instantiateController_addExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ eq(IStartingWindow.DESCRIPTOR), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 2442a55d78d0..dd645fd968e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -110,7 +110,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.recents.IRecentsAnimationRunner;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.shared.ShellSharedConstants;
+import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -176,7 +176,7 @@ public class ShellTransitionTests extends ShellTestCase {
mock(FocusTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ eq(IShellTransitions.DESCRIPTOR), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
index 0e15668a05a7..a328b5b2bb6b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
@@ -77,6 +77,30 @@ class TransitionObserverTestContext : TransitionObserverTestStep {
validateObj.validate()
}
+ fun validateOnMerged(
+ validate:
+ TransitionObserverOnTransitionMergedValidation.() -> Unit
+ ) {
+ val validateObj = TransitionObserverOnTransitionMergedValidation()
+ transitionObserver.onTransitionMerged(
+ validateObj.playing,
+ validateObj.merged
+ )
+ validateObj.validate()
+ }
+
+ fun validateOnFinished(
+ validate:
+ TransitionObserverOnTransitionFinishedValidation.() -> Unit
+ ) {
+ val validateObj = TransitionObserverOnTransitionFinishedValidation()
+ transitionObserver.onTransitionFinished(
+ transitionReadyInput.transition,
+ validateObj.aborted
+ )
+ validateObj.validate()
+ }
+
fun invokeObservable() {
transitionObserver.onTransitionReady(
transitionReadyInput.transition,
@@ -162,6 +186,28 @@ class TransitionObserverInputBuilder : TransitionObserverTestStep {
class TransitionObserverResultValidation : TransitionObserverTestStep
/**
+ * Phase responsible for the execution of validation methods after the
+ * [TransitionObservable#onTransitionMerged] has been executed.
+ */
+class TransitionObserverOnTransitionMergedValidation : TransitionObserverTestStep {
+ val merged = mock<IBinder>()
+ val playing = mock<IBinder>()
+
+ init {
+ spyOn(merged)
+ spyOn(playing)
+ }
+}
+
+/**
+ * Phase responsible for the execution of validation methods after the
+ * [TransitionObservable#onTransitionFinished] has been executed.
+ */
+class TransitionObserverOnTransitionFinishedValidation : TransitionObserverTestStep {
+ var aborted: Boolean = false
+}
+
+/**
* Allows to run a test about a specific [TransitionObserver] passing the specific
* implementation and input value as parameters for the [TransitionObserver#onTransitionReady]
* method.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 59141ca39487..b856a28e54db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -48,6 +48,7 @@ class CaptionWindowDecorationTests : ShellTestCase() {
CaptionWindowDecoration.updateRelayoutParams(
relayoutParams,
+ mContext,
taskInfo,
true,
false,
@@ -71,6 +72,7 @@ class CaptionWindowDecorationTests : ShellTestCase() {
CaptionWindowDecoration.updateRelayoutParams(
relayoutParams,
+ mContext,
taskInfo,
true,
false,
@@ -90,6 +92,7 @@ class CaptionWindowDecorationTests : ShellTestCase() {
val relayoutParams = WindowDecoration.RelayoutParams()
CaptionWindowDecoration.updateRelayoutParams(
relayoutParams,
+ mContext,
taskInfo,
true,
false,
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 1215c52209a5..e871711fd25e 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
@@ -29,7 +29,7 @@ import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.google.common.truth.Truth.assertThat
@@ -55,17 +55,18 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
@Rule
val setFlagsRule: SetFlagsRule = SetFlagsRule()
- private lateinit var desktopRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
private lateinit var menu: DesktopHeaderManageWindowsMenu
@Before
fun setUp() {
- desktopRepository = DesktopRepository(
+ userRepositories = DesktopUserRepositories(
context = context,
shellInit = ShellInit(TestShellExecutor()),
persistentRepository = mock(),
repositoryInitializer = mock(),
- mainCoroutineScope = mock()
+ mainCoroutineScope = mock(),
+ userManager = mock(),
)
}
@@ -78,12 +79,11 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun testShow_forImmersiveTask_usesSystemViewContainer() {
val task = createFreeformTask()
- desktopRepository.setTaskInFullImmersiveState(
+ userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
immersive = true
)
-
menu = createMenu(task)
assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java)
@@ -96,7 +96,7 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
displayController = mock(),
rootTdaOrganizer = mock(),
context = context,
- desktopRepository = desktopRepository,
+ desktopUserRepositories = userRepositories,
surfaceControlBuilderSupplier = { SurfaceControl.Builder() },
surfaceControlTransactionSupplier = { SurfaceControl.Transaction() },
snapshotList = emptyList(),
@@ -108,5 +108,10 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setUserId(DEFAULT_USER_ID)
.build()
+
+ private companion object {
+ const val DEFAULT_USER_ID = 10
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index a15b61122713..8b4cf6d1fabe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -125,6 +125,8 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
times(1)
).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any())
openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId)
+ bgExecutor.flushAll()
+ testShellExecutor.flushAll()
verify(decor, times(1)).createHandleMenu(anyBoolean())
}
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 153be07bd204..88f62d10913d 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
@@ -59,6 +59,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -277,7 +278,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForTopTranslucentActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -398,11 +399,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
maxOrRestoreListenerCaptor.value.invoke()
verify(mockDesktopTasksController).toggleDesktopTaskSize(
- eq(decor.mTaskInfo),
- eq(ResizeTrigger.MAXIMIZE_MENU),
- eq(InputMethod.UNKNOWN_INPUT_METHOD),
- any(),
- any()
+ decor.mTaskInfo,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE,
+ InputMethod.UNKNOWN_INPUT_METHOD
+ )
)
}
@@ -778,6 +780,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
times(1)
).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any())
openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId)
+ bgExecutor.flushAll()
+ testShellExecutor.flushAll()
verify(decor, times(1)).createHandleMenu(anyBoolean())
}
@@ -1061,11 +1065,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
verify(mockDesktopTasksController)
.toggleDesktopTaskSize(
- eq(decor.mTaskInfo),
- eq(ResizeTrigger.MAXIMIZE_BUTTON),
- eq(InputMethod.UNKNOWN_INPUT_METHOD),
- any(),
- any(),
+ decor.mTaskInfo,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.UNKNOWN_INPUT_METHOD
+ )
)
}
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 080f496593cf..6be234ef5ca6 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
@@ -60,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
import com.android.wm.shell.desktopmode.education.AppHandleEducationController
import com.android.wm.shell.desktopmode.education.AppToWebEducationController
@@ -73,6 +74,8 @@ 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.viewhost.WindowDecorViewHost
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.junit.Rule
@@ -108,7 +111,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockTaskOrganizer = mock<ShellTaskOrganizer>()
protected val mockDisplayController = mock<DisplayController>()
protected val mockSplitScreenController = mock<SplitScreenController>()
- protected val mockDesktopRepository = mock<DesktopRepository>()
+ protected val mockDesktopUserRepositories = mock<DesktopUserRepositories>()
protected val mockDisplayLayout = mock<DisplayLayout>()
protected val displayInsetsController = mock<DisplayInsetsController>()
protected val mockSyncQueue = mock<SyncTransactionQueue>()
@@ -130,6 +133,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockAssistContentRequester = mock<AssistContentRequester>()
protected val bgExecutor = TestShellExecutor()
protected val mockMultiInstanceHelper = mock<MultiInstanceHelper>()
+ private val mockWindowDecorViewHostSupplier =
+ mock<WindowDecorViewHostSupplier<WindowDecorViewHost>>()
protected val mockTasksLimiter = mock<DesktopTasksLimiter>()
protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
protected val mockActivityOrientationChangeHandler =
@@ -142,6 +147,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val mockAppToWebEducationController = mock<AppToWebEducationController>()
protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
+ protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
protected val motionEvent = mock<MotionEvent>()
val displayController = mock<DisplayController>()
val displayLayout = mock<DisplayLayout>()
@@ -168,6 +174,9 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
windowDecorByTaskIdSpy.clear()
spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
desktopModeEventLogger = mock<DesktopModeEventLogger>()
+ whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
+ whenever(mockDesktopUserRepositories.getProfile(anyInt()))
+ .thenReturn(mockDesktopRepository)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
testShellExecutor,
@@ -178,7 +187,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockShellCommandHandler,
mockWindowManager,
mockTaskOrganizer,
- mockDesktopRepository,
+ mockDesktopUserRepositories,
mockDisplayController,
mockShellController,
displayInsetsController,
@@ -188,6 +197,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockDesktopImmersiveController,
mockGenericLinksParser,
mockAssistContentRequester,
+ mockWindowDecorViewHostSupplier,
mockMultiInstanceHelper,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
@@ -276,6 +286,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
} else {
statusBars()
}
+ userId = context.userId
}
}
@@ -284,7 +295,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
whenever(
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any())
+ any(), any(), any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
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 e390fbbd751f..5d5d1f220ae0 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
@@ -29,6 +29,7 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS;
@@ -38,7 +39,6 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -50,7 +50,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.kotlin.VerificationKt.times;
import android.app.ActivityManager;
import android.app.assist.AssistContent;
@@ -61,7 +60,6 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -110,10 +108,13 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
@@ -166,7 +167,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private DesktopRepository mMockDesktopRepository;
+ private DesktopUserRepositories mMockDesktopUserRepositories;
@Mock
private Choreographer mMockChoreographer;
@Mock
@@ -186,6 +187,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
@@ -215,6 +220,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
@Mock
private DesktopModeEventLogger mDesktopModeEventLogger;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -271,6 +278,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
any())).thenReturn(mMockAppHeaderViewHolder);
+ when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+ when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@After
@@ -295,12 +307,35 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
- public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mShadowRadius)
+ .isNotEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
+ }
+
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
RelayoutParams relayoutParams = new RelayoutParams();
DesktopModeWindowDecoration.updateRelayoutParams(
- relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true,
+ relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
@@ -309,7 +344,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* hasGlobalFocus= */ true,
mExclusionRegion);
- assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+ assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
+ }
+
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
}
@Test
@@ -323,6 +378,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -346,6 +402,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -373,6 +454,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -401,6 +483,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -426,6 +509,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -451,6 +535,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -475,6 +560,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -499,6 +585,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -522,6 +609,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -545,6 +633,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -567,6 +656,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -590,6 +680,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -613,6 +704,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -637,6 +729,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -662,6 +755,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -685,6 +779,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -710,6 +805,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -725,6 +821,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
+ public void updateRelayoutParams_handle_bottomSplitIsInsetSource() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+ when(mMockSplitScreenController.isLeftRightSplit()).thenReturn(false);
+ when(mMockSplitScreenController.getSplitPosition(taskInfo.taskId))
+ .thenReturn(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ true,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mIsInsetSource).isTrue();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
public void updateRelayoutParams_header_addsPaddingInFullImmersive() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -741,6 +862,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -765,6 +887,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -788,6 +911,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ false,
@@ -811,6 +935,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -833,6 +958,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ false,
@@ -855,6 +981,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -878,6 +1005,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -893,6 +1021,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ false,
@@ -916,6 +1045,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
relayoutParams,
mTestableContext,
taskInfo,
+ mMockSplitScreenController,
/* applyStartTransactionOnDraw= */ true,
/* shouldSetTaskPositionAndCrop */ false,
/* isStatusBarVisible */ true,
@@ -929,61 +1059,70 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@Test
- public void relayout_fullscreenTask_appliesTransactionImmediately() {
+ public void updateRelayoutParams_handle_requestsAsyncViewHostRendering() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ // Make the task fullscreen so that its decoration is an App Handle.
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop= */ false,
+ /* isStatusBarVisible= */ true,
+ /* isKeyguardVisibleAndOccluded= */ false,
+ /* inFullImmersiveMode= */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
- verify(mMockTransaction).apply();
- verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
+ // App Handles don't need to be rendered in sync with the task animation, per UX.
+ assertThat(relayoutParams.mAsyncViewHost).isTrue();
}
@Test
- @Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error")
- public void relayout_freeformTask_appliesTransactionOnDraw() {
+ public void updateRelayoutParams_header_requestsSyncViewHostRendering() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
+ // Make the task freeform so that its decoration is an App Header.
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
- taskInfo.isResizeable = false;
-
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
-
- verify(mMockTransaction, never()).apply();
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
- }
-
- @Test
- public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ mMockSplitScreenController,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop= */ false,
+ /* isStatusBarVisible= */ true,
+ /* isKeyguardVisibleAndOccluded= */ false,
+ /* inFullImmersiveMode= */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
- verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
+ // App Headers must be rendered in sync with the task animation, so it cannot be delayed.
+ assertThat(relayoutParams.mAsyncViewHost).isFalse();
}
@Test
- public void relayout_fullscreenTask_postsViewHostCreation() {
+ public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
- runnableArgument.getValue().run();
- verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
+ verify(mMockTransaction).apply();
+ verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
}
@Test
@Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error")
- public void relayout_freeformTask_createsViewHostImmediately() {
+ public void relayout_freeformTask_appliesTransactionOnDraw() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -992,38 +1131,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
- verify(mMockHandler, never()).post(any());
- }
-
- @Test
- public void relayout_removesExistingHandlerCallback() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
-
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
-
- verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
- }
-
- @Test
- public void close_removesExistingHandlerCallback() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- // Once for view host, the other for the AppHandle input layer.
- verify(mMockHandler, times(2)).post(runnableArgument.capture());
-
- spyWindowDecor.close();
-
- verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
+ verify(mMockTransaction, never()).apply();
+ verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
}
@Test
@@ -1226,68 +1335,41 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(
- taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
- null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
-
- // Verify captured link is no longer valid by verifying link is not set as handle menu
- // browser link.
- createHandleMenu(decor);
- verifyHandleMenuCreated(null /* uri */);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_capturedLinkNotResetToSameLink() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+ final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
+ createHandleMenu(decor);
+ verify(mMockHandleMenu).show(any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any(),
+ any(),
+ anyBoolean()
+ );
+ // Run runnable to set captured link to used
+ openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
// Relayout decor with same captured link
decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
- // Verify handle menu's browser link not set to captured link since link is expired
+ // Verify handle menu's browser link not set to captured link since link is already used
createHandleMenu(decor);
verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
- final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(
- taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
- null /* generic link */);
- final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
-
- // Create handle menu before link expires
- createHandleMenu(decor);
-
- // Run runnable to set captured link to expired
- verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
- runnableArgument.getValue().run();
-
- // Verify handle menu's browser link is set to captured link since menu was opened before
- // captured link expired
- verifyHandleMenuCreated(TEST_URI1);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_capturedLinkExpiresAfterClick() {
+ public void capturedLink_capturedLinkSetToUsedAfterClick() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
@@ -1407,8 +1489,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
true /* relayout */);
- when(mMockDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId))
- .thenReturn(true);
+ when(mMockDesktopUserRepositories.getCurrent()
+ .isTaskInFullImmersiveState(taskInfo.taskId)).thenReturn(true);
createHandleMenu(decoration);
@@ -1429,7 +1511,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Test
@DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION})
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION})
public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1643,13 +1725,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
boolean relayout) {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mContext, mMockDisplayController, mMockSplitScreenController,
- mMockDesktopRepository, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
- mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue,
+ mMockDesktopUserRepositories, mMockShellTaskOrganizer, taskInfo,
+ mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue,
mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer,
mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory, mMockHandleMenuFactory,
+ mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
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 534803db5fe0..d9693460008f 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
@@ -61,6 +61,7 @@ import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Handler;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -88,6 +89,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import org.junit.Before;
import org.junit.Rule;
@@ -114,6 +117,7 @@ public class WindowDecorationTests extends ShellTestCase {
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
private static final int CORNER_RADIUS = 20;
+ private static final int SHADOW_RADIUS = 10;
private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
@Rule
@@ -129,6 +133,10 @@ public class WindowDecorationTests extends ShellTestCase {
@Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@@ -142,6 +150,8 @@ public class WindowDecorationTests extends ShellTestCase {
private SurfaceControl mMockTaskSurface;
@Mock
private DesktopModeEventLogger mDesktopModeEventLogger;
+ @Mock
+ private Handler mMockHandler;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -162,7 +172,7 @@ public class WindowDecorationTests extends ShellTestCase {
mRelayoutParams.mLayoutResId = 0;
mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
- mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+ mRelayoutParams.mShadowRadius = SHADOW_RADIUS;
mRelayoutParams.mCornerRadius = CORNER_RADIUS;
when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
@@ -176,6 +186,10 @@ public class WindowDecorationTests extends ShellTestCase {
// Add status bar inset so that WindowDecoration does not think task is in immersive mode
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), any()))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@Test
@@ -232,10 +246,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
@@ -256,18 +266,19 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
-
- verify(mMockSurfaceControlViewHost)
- .setView(same(mMockView),
- argThat(lp -> lp.height == 64
- && lp.width == 300
- && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+ verify(mMockWindowDecorViewHost).updateView(
+ same(mMockView),
+ argThat(lp -> lp.height == 64
+ && lp.width == 300
+ && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0),
+ eq(taskInfo.configuration),
+ any(),
+ eq(null) /* onDrawTransaction */);
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction).addInsetsSource(
eq(taskInfo.token),
@@ -280,7 +291,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -296,10 +307,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -322,7 +329,7 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockSurfaceControlViewHost, never()).release();
+ verify(mMockWindowDecorViewHost, never()).release(any());
verify(t, never()).apply();
verify(mMockWindowContainerTransaction, never())
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -332,9 +339,8 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
- final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
- releaseOrder.verify(mMockSurfaceControlViewHost).release();
- releaseOrder.verify(t2).remove(captionContainerSurface);
+ final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier);
+ releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2);
releaseOrder.verify(t2).remove(decorContainerSurface);
releaseOrder.verify(t2).apply();
// Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
@@ -382,8 +388,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
- verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+ verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay));
+ verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any());
}
@Test
@@ -396,10 +402,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -435,8 +437,7 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
- verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
- .create(any(), eq(defaultDisplay), any());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
}
@Test
@@ -449,10 +450,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -471,8 +468,8 @@ public class WindowDecorationTests extends ShellTestCase {
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -488,10 +485,6 @@ public class WindowDecorationTests extends ShellTestCase {
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -508,10 +501,11 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */, Region.obtain());
+ mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), any(),
+ eq(mMockSurfaceControlStartT));
}
@Test
@@ -900,37 +894,69 @@ public class WindowDecorationTests extends ShellTestCase {
}
@Test
- public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() {
+ public void relayout_applyTransactionOnDrawIsTrue_updatesViewWithDrawTransaction() {
final TestWindowDecoration windowDecor = createWindowDecoration(
- new TestRunningTaskInfoBuilder().build());
+ new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
mRelayoutResult.mRootView = mMockView;
- windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult);
-
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ windowDecor.relayout(
+ windowDecor.mTaskInfo,
+ /* hasGlobalFocus= */ true,
+ Region.obtain());
+
+ verify(mMockWindowDecorViewHost)
+ .updateView(
+ eq(mRelayoutResult.mRootView),
+ any(),
+ eq(windowDecor.mTaskInfo.configuration),
+ any(),
+ eq(mMockSurfaceControlStartT));
+ windowDecor.close();
}
@Test
- public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() {
+ public void relayout_applyTransactionOnDrawIsTrue_asyncViewHostRendering_throwsException() {
final TestWindowDecoration windowDecor = createWindowDecoration(
- new TestRunningTaskInfoBuilder().build());
+ new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ mRelayoutParams.mAsyncViewHost = true;
mRelayoutResult.mRootView = mMockView;
assertThrows(IllegalArgumentException.class,
- () -> windowDecor.updateViewHost(
- mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult));
+ () -> windowDecor.relayout(
+ windowDecor.mTaskInfo,
+ /* hasGlobalFocus= */ true,
+ Region.obtain()));
+ windowDecor.close();
}
@Test
- public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() {
+ public void relayout_asyncViewHostRendering() {
final TestWindowDecoration windowDecor = createWindowDecoration(
- new TestRunningTaskInfoBuilder().build());
+ new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build());
mRelayoutParams.mApplyStartTransactionOnDraw = false;
+ mRelayoutParams.mAsyncViewHost = true;
mRelayoutResult.mRootView = mMockView;
- windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
+ windowDecor.relayout(
+ windowDecor.mTaskInfo,
+ /* hasGlobalFocus= */ true,
+ Region.obtain());
+
+ verify(mMockWindowDecorViewHost)
+ .updateViewAsync(eq(mRelayoutResult.mRootView), any(),
+ eq(windowDecor.mTaskInfo.configuration), any());
+ windowDecor.close();
}
@Test
@@ -1014,7 +1040,8 @@ public class WindowDecorationTests extends ShellTestCase {
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
() -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
- mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
+ mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier,
+ mDesktopModeEventLogger);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1048,18 +1075,22 @@ public class WindowDecorationTests extends ShellTestCase {
private class TestWindowDecoration extends WindowDecoration<TestView> {
TestWindowDecoration(Context context, @NonNull Context userContext,
DisplayController displayController,
- ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
- surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier,
+ desktopModeEventLogger);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
@@ -1070,8 +1101,12 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion) {
- relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus,
- displayExclusionRegion);
+ mRelayoutParams.mRunningTaskInfo = taskInfo;
+ mRelayoutParams.mHasGlobalFocus = hasGlobalFocus;
+ mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
+ mRelayoutParams.mLayoutResId = R.layout.caption_layout;
+ relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
+ mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
@Override
@@ -1095,13 +1130,8 @@ public class WindowDecorationTests extends ShellTestCase {
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw, boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion) {
- mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- mRelayoutParams.mLayoutResId = R.layout.caption_layout;
- mRelayoutParams.mHasGlobalFocus = hasGlobalFocus;
- mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
- relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
- mMockWindowContainerTransaction, mMockView, mRelayoutResult);
+ relayout(taskInfo, hasGlobalFocus, displayExclusionRegion);
}
private AdditionalViewContainer addTestViewContainer() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
new file mode 100644
index 000000000000..4f19f34b8370
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [DefaultWindowDecorViewHost].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DefaultWindowDecorViewHostTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DefaultWindowDecorViewHostTest : ShellTestCase() {
+
+ @Test
+ fun updateView_layoutInViewHost() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null,
+ )
+
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isEqualTo(view)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateView_clearsPendingAsyncJob() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val asyncView = View(context)
+ val syncView = View(context)
+ val asyncAttrs = WindowManager.LayoutParams(100, 100)
+ val syncAttrs = WindowManager.LayoutParams(200, 200)
+
+ windowDecorViewHost.updateViewAsync(
+ view = asyncView,
+ attrs = asyncAttrs,
+ configuration = context.resources.configuration,
+ )
+
+ // No view host yet, since the coroutine hasn't run.
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+
+ windowDecorViewHost.updateView(
+ view = syncView,
+ attrs = syncAttrs,
+ configuration = context.resources.configuration,
+ onDrawTransaction = null,
+ )
+
+ // Would run coroutine if it hadn't been cancelled.
+ advanceUntilIdle()
+
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isNotNull()
+ // View host view/attrs should match the ones from the sync call, plus, since the
+ // sync/async were made with different views, if the job hadn't been cancelled there
+ // would've been an exception thrown as replacing views isn't allowed.
+ assertThat(windowDecorViewHost.view()).isEqualTo(syncView)
+ assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateViewAsync() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+ val attrs = WindowManager.LayoutParams(100, 100)
+
+ windowDecorViewHost.updateViewAsync(
+ view = view,
+ attrs = attrs,
+ configuration = context.resources.configuration,
+ )
+
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+
+ advanceUntilIdle()
+
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateViewAsync_clearsPendingAsyncJob() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+
+ val view = View(context)
+ windowDecorViewHost.updateViewAsync(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ )
+ val otherView = View(context)
+ windowDecorViewHost.updateViewAsync(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ )
+
+ advanceUntilIdle()
+
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isEqualTo(otherView)
+ }
+
+ @Test
+ fun release() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+
+ val view = View(context)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null,
+ )
+
+ val t = mock(SurfaceControl.Transaction::class.java)
+ windowDecorViewHost.release(t)
+
+ verify(windowDecorViewHost.viewHostAdapter).release(t)
+ }
+
+ private fun CoroutineScope.createDefaultViewHost() =
+ DefaultWindowDecorViewHost(
+ context = context,
+ mainScope = this,
+ display = context.display,
+ viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
+ )
+
+ private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt
new file mode 100644
index 000000000000..40583f80003c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.util.StubTransaction
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [PooledWindowDecorViewHostSupplier].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PooledWindowDecorViewHostSupplierTest : ShellTestCase() {
+
+ private lateinit var supplier: PooledWindowDecorViewHostSupplier
+
+ @Test
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun acquire_poolBelowLimit_caches() = runTest {
+ supplier = createSupplier(maxPoolSize = 5)
+
+ val viewHost = FakeWindowDecorViewHost()
+ supplier.release(viewHost, StubTransaction())
+
+ assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost)
+ }
+
+ @Test
+ fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest {
+ supplier = createSupplier(maxPoolSize = 5)
+
+ val viewHost = FakeWindowDecorViewHost()
+ val mockT = mock<SurfaceControl.Transaction>()
+ supplier.release(viewHost, mockT)
+
+ assertThat(viewHost.released).isFalse()
+ }
+
+ @Test
+ fun release_poolAtLimit_doesNotCache() = runTest {
+ supplier = createSupplier(maxPoolSize = 1)
+ val viewHost = FakeWindowDecorViewHost()
+ supplier.release(viewHost, StubTransaction()) // Maxes pool.
+
+ val viewHost2 = FakeWindowDecorViewHost()
+ supplier.release(viewHost2, StubTransaction()) // Beyond limit.
+
+ assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost)
+ // Second one wasn't cached, so the acquired one should've been a new instance.
+ assertThat(supplier.acquire(context, context.display)).isNotEqualTo(viewHost2)
+ }
+
+ @Test
+ fun release_poolAtLimit_releasesViewHost() = runTest {
+ supplier = createSupplier(maxPoolSize = 1)
+ val viewHost = FakeWindowDecorViewHost()
+ supplier.release(viewHost, StubTransaction()) // Maxes pool.
+
+ val viewHost2 = FakeWindowDecorViewHost()
+ val mockT = mock<SurfaceControl.Transaction>()
+ supplier.release(viewHost2, mockT) // Beyond limit.
+
+ // Second one doesn't fit, so it needs to be released.
+ assertThat(viewHost2.released).isTrue()
+ }
+
+ private fun CoroutineScope.createSupplier(maxPoolSize: Int) =
+ PooledWindowDecorViewHostSupplier(this, maxPoolSize)
+
+ private class FakeWindowDecorViewHost : WindowDecorViewHost {
+ var released = false
+ private set
+
+ override val surfaceControl: SurfaceControl
+ get() = SurfaceControl()
+
+ override fun updateView(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ onDrawTransaction: SurfaceControl.Transaction?,
+ ) {}
+
+ override fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region?,
+ ) {}
+
+ override fun release(t: SurfaceControl.Transaction) {
+ released = true
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
new file mode 100644
index 000000000000..245393a6d44e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [ReusableWindowDecorViewHost].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:ReusableWindowDecorViewHostTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class ReusableWindowDecorViewHostTest : ShellTestCase() {
+
+ @Test
+ fun update_differentView_replacesView() = runTest {
+ val view = View(context)
+ val lp = WindowManager.LayoutParams()
+ val reusableVH = createReusableViewHost()
+ reusableVH.updateView(view, lp, context.resources.configuration, null)
+
+ assertThat(reusableVH.rootView.childCount).isEqualTo(1)
+ assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view)
+
+ val newView = View(context)
+ val newLp = WindowManager.LayoutParams()
+ reusableVH.updateView(newView, newLp, context.resources.configuration, null)
+
+ assertThat(reusableVH.rootView.childCount).isEqualTo(1)
+ assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateView_clearsPendingAsyncJob() = runTest {
+ val reusableVH = createReusableViewHost()
+ val asyncView = View(context)
+ val syncView = View(context)
+ val asyncAttrs = WindowManager.LayoutParams(100, 100)
+ val syncAttrs = WindowManager.LayoutParams(200, 200)
+
+ reusableVH.updateViewAsync(
+ view = asyncView,
+ attrs = asyncAttrs,
+ configuration = context.resources.configuration,
+ )
+
+ // No view host yet, since the coroutine hasn't run.
+ assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
+
+ reusableVH.updateView(
+ view = syncView,
+ attrs = syncAttrs,
+ configuration = context.resources.configuration,
+ onDrawTransaction = null,
+ )
+
+ // Would run coroutine if it hadn't been cancelled.
+ advanceUntilIdle()
+
+ assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
+ // View host view/attrs should match the ones from the sync call.
+ assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView)
+ assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateViewAsync() = runTest {
+ val reusableVH = createReusableViewHost()
+ val view = View(context)
+ val attrs = WindowManager.LayoutParams(100, 100)
+
+ reusableVH.updateViewAsync(
+ view = view,
+ attrs = attrs,
+ configuration = context.resources.configuration,
+ )
+
+ assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
+
+ advanceUntilIdle()
+
+ assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun updateViewAsync_clearsPendingAsyncJob() = runTest {
+ val reusableVH = createReusableViewHost()
+
+ val view = View(context)
+ reusableVH.updateViewAsync(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ )
+ val otherView = View(context)
+ reusableVH.updateViewAsync(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ )
+
+ advanceUntilIdle()
+
+ assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView)
+ }
+
+ @Test
+ fun release() = runTest {
+ val reusableVH = createReusableViewHost()
+
+ val view = View(context)
+ reusableVH.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null,
+ )
+
+ val t = mock(SurfaceControl.Transaction::class.java)
+ reusableVH.release(t)
+
+ verify(reusableVH.viewHostAdapter).release(t)
+ }
+
+ private fun CoroutineScope.createReusableViewHost() =
+ ReusableWindowDecorViewHost(
+ context = context,
+ mainScope = this,
+ display = context.display,
+ id = 1,
+ viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
+ )
+
+ private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
new file mode 100644
index 000000000000..5109a7c300f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.windowdecor.common.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [SurfaceControlViewHostAdapter].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class SurfaceControlViewHostAdapterTest : ShellTestCase() {
+
+ private lateinit var adapter: SurfaceControlViewHostAdapter
+
+ @Before
+ fun setUp() {
+ adapter = SurfaceControlViewHostAdapter(
+ context,
+ context.display,
+ surfaceControlViewHostFactory = { c, d, wwm, s ->
+ spy(SurfaceControlViewHost(c, d, wwm, s))
+ }
+ )
+ }
+
+ @Test
+ fun prepareViewHost() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ assertThat(adapter.viewHost).isNotNull()
+ }
+
+ @Test
+ fun prepareViewHost_alreadyCreated_skips() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ val viewHost = adapter.viewHost!!
+
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ assertThat(adapter.viewHost).isEqualTo(viewHost)
+ }
+
+ @Test
+ fun updateView_layoutInViewHost() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ assertThat(adapter.isInitialized()).isTrue()
+ assertThat(adapter.view()).isEqualTo(view)
+ }
+
+ @Test
+ fun updateView_alreadyLaidOut_relayouts() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val otherParams = WindowManager.LayoutParams(200, 200)
+ adapter.updateView(
+ view = view,
+ attrs = otherParams
+ )
+
+ assertThat(adapter.view()).isEqualTo(view)
+ assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width)
+ }
+
+ @Test
+ fun updateView_replacingView_throws() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val otherView = View(context)
+ assertThrows(Exception::class.java) {
+ adapter.updateView(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+ }
+ }
+
+ @Test
+ fun release() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = View(context),
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val mockT = mock(SurfaceControl.Transaction::class.java)
+ adapter.release(mockT)
+
+ verify(adapter.viewHost!!).release()
+ verify(mockT).remove(adapter.rootSurface)
+ }
+
+ private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index d29002199f9d..193c2c25d26d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -25,7 +25,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -52,7 +52,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
private val syncQueueMock: SyncTransactionQueue = mock()
private val transitionsMock: Transitions = mock()
private val shellTaskOrganizerMock: ShellTaskOrganizer = mock()
- private val desktopRepository: DesktopRepository = mock()
+ private val userRepositories: DesktopUserRepositories = mock()
private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val toggleResizeDesktopTaskTransitionHandlerMock:
ToggleResizeDesktopTaskTransitionHandler =
@@ -75,7 +75,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
shellTaskOrganizerMock,
toggleResizeDesktopTaskTransitionHandlerMock,
returnToDragStartAnimatorMock,
- desktopRepository,
+ userRepositories,
desktopModeEventLogger,
)
whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
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 3b39f1e4a25a..95e2151be96c 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
@@ -39,6 +39,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
import com.android.wm.shell.transition.Transitions
@@ -93,10 +94,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private val transition: IBinder = mock()
private val info: TransitionInfo = mock()
private val finishCallback: Transitions.TransitionFinishCallback = mock()
- private val desktopRepository: DesktopRepository = mock()
+ private val userRepositories: DesktopUserRepositories = mock()
private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock()
private val motionEvent: MotionEvent = mock()
+ private val desktopRepository: DesktopRepository = mock()
private lateinit var tilingDecoration: DesktopTilingWindowDecoration
private val split_divider_width = 10
@@ -116,10 +118,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository,
+ userRepositories,
desktopModeEventLogger,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
+ whenever(userRepositories.current).thenReturn(desktopRepository)
}
@Test
@@ -275,8 +278,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
whenever(context.resources).thenReturn(resources)
whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
- whenever(desktopRepository.isVisibleTask(eq(task1.taskId))).thenReturn(true)
- whenever(desktopRepository.isVisibleTask(eq(task2.taskId))).thenReturn(true)
+ whenever(userRepositories.current.isVisibleTask(eq(task1.taskId))).thenReturn(true)
+ whenever(userRepositories.current.isVisibleTask(eq(task2.taskId))).thenReturn(true)
tilingDecoration.onAppTiled(
task1,
@@ -308,7 +311,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
whenever(context.resources).thenReturn(resources)
whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock)
- whenever(desktopRepository.isVisibleTask(any())).thenReturn(true)
+ whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true)
tilingDecoration.onAppTiled(
task1,
desktopWindowDecoration,
@@ -341,7 +344,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
whenever(context.resources).thenReturn(resources)
whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock)
- whenever(desktopRepository.isVisibleTask(any())).thenReturn(true)
+ whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true)
tilingDecoration.onAppTiled(
task1,
desktopWindowDecoration,
@@ -614,7 +617,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private fun createVisibleTask() =
createFreeformTask().also {
- whenever(desktopRepository.isVisibleTask(eq(it.taskId))).thenReturn(true)
+ whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
}
companion object {
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 49254d1c6f6e..dbb891455ddd 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -40,20 +40,21 @@ ApkAssets::ApkAssets(PrivateConstructorUtil, std::unique_ptr<Asset> resources_as
}
ApkAssetsPtr ApkAssets::Load(const std::string& path, package_property_t flags) {
- return Load(ZipAssetsProvider::Create(path, flags), flags);
+ return LoadImpl(ZipAssetsProvider::Create(path, flags), flags);
}
ApkAssetsPtr ApkAssets::LoadFromFd(base::unique_fd fd, const std::string& debug_name,
package_property_t flags, off64_t offset, off64_t len) {
- return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
+ return LoadImpl(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
}
-ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) {
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
+ package_property_t flags) {
return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
}
-ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
- std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset>&& resources_asset,
+ std::unique_ptr<AssetsProvider>&& assets,
package_property_t flags) {
if (resources_asset == nullptr) {
return {};
@@ -97,10 +98,10 @@ ApkAssetsPtr ApkAssets::LoadOverlay(const std::string& idmap_path, package_prope
std::move(loaded_idmap));
}
-ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
package_property_t property_flags,
- std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ std::unique_ptr<Asset>&& idmap_asset,
+ std::unique_ptr<LoadedIdmap>&& loaded_idmap) {
if (assets == nullptr) {
return {};
}
@@ -119,11 +120,11 @@ ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
std::move(idmap_asset), std::move(loaded_idmap));
}
-ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
- std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset>&& resources_asset,
+ std::unique_ptr<AssetsProvider>&& assets,
package_property_t property_flags,
- std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<LoadedIdmap> loaded_idmap) {
+ std::unique_ptr<Asset>&& idmap_asset,
+ std::unique_ptr<LoadedIdmap>&& loaded_idmap) {
if (assets == nullptr ) {
return {};
}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 5e645cceea2d..a592749c5398 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -38,7 +38,7 @@ CursorWindow::CursorWindow() {
}
CursorWindow::~CursorWindow() {
- if (mAshmemFd != -1) {
+ if (mAshmemFd >= 0) {
::munmap(mData, mSize);
::close(mAshmemFd);
} else {
@@ -155,23 +155,27 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow
bool isAshmem;
if (parcel->readBool(&isAshmem)) goto fail;
if (isAshmem) {
- window->mAshmemFd = parcel->readFileDescriptor();
- if (window->mAshmemFd < 0) {
+ int tempFd = parcel->readFileDescriptor();
+ if (tempFd < 0) {
LOG(ERROR) << "Failed readFileDescriptor";
goto fail_silent;
}
- window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0);
- if (window->mAshmemFd < 0) {
+ tempFd = ::fcntl(tempFd, F_DUPFD_CLOEXEC, 0);
+ if (tempFd < 0) {
PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC";
goto fail_silent;
}
- window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0);
+ window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, tempFd, 0);
if (window->mData == MAP_FAILED) {
+ ::close(tempFd);
PLOG(ERROR) << "Failed mmap";
goto fail_silent;
}
+
+ window->mAshmemFd = tempFd;
+
} else {
window->mAshmemFd = -1;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 1fa67528c78b..231808beb718 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -47,13 +47,37 @@ class ApkAssets : public RefBase {
package_property_t flags = 0U, off64_t offset = 0,
off64_t len = AssetsProvider::kUnknownLength);
+ //
// Creates an ApkAssets from an AssetProvider.
- // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed.
- static ApkAssetsPtr Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags = 0U);
+ // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed;
+ // the original argument is not moved from if loading fails.
+ //
+ // Note: this function takes care of the case when you pass a move(unique_ptr<Derived>)
+ // that would create a temporary unique_ptr<AssetsProvider> by moving your pointer into
+ // it before the function call, making it impossible to not move from the parameter
+ // on loading failure. The two overloads take care of moving the pointer back if needed.
+ //
+
+ template <class T>
+ static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U)
+ requires(std::is_same_v<T, AssetsProvider>) {
+ return LoadImpl(std::move(assets), flags);
+ }
+
+ template <class T>
+ static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U)
+ requires(!std::is_same_v<T, AssetsProvider> && std::is_base_of_v<AssetsProvider, T>) {
+ std::unique_ptr<AssetsProvider> base_assets(std::move(assets));
+ auto res = LoadImpl(std::move(base_assets), flags);
+ if (!res) {
+ assets.reset(static_cast<T*>(base_assets.release()));
+ }
+ return res;
+ }
// Creates an ApkAssets from the given asset file representing a resources.arsc.
- static ApkAssetsPtr LoadTable(std::unique_ptr<Asset> resources_asset,
- std::unique_ptr<AssetsProvider> assets,
+ static ApkAssetsPtr LoadTable(std::unique_ptr<Asset>&& resources_asset,
+ std::unique_ptr<AssetsProvider>&& assets,
package_property_t flags = 0U);
// Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
@@ -94,17 +118,29 @@ class ApkAssets : public RefBase {
bool IsUpToDate() const;
+ // DANGER!
+ // This is a destructive method that rips the assets provider out of ApkAssets object.
+ // It is only useful when one knows this assets object can't be used anymore, and they
+ // need the underlying assets provider back (e.g. when initialization fails for some
+ // reason).
+ std::unique_ptr<AssetsProvider> TakeAssetsProvider() && {
+ return std::move(assets_provider_);
+ }
+
private:
- static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider> assets,
+ static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
package_property_t property_flags,
- std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<LoadedIdmap> loaded_idmap);
+ std::unique_ptr<Asset>&& idmap_asset,
+ std::unique_ptr<LoadedIdmap>&& loaded_idmap);
- static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset> resources_asset,
- std::unique_ptr<AssetsProvider> assets,
+ static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset>&& resources_asset,
+ std::unique_ptr<AssetsProvider>&& assets,
package_property_t property_flags,
- std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<LoadedIdmap> loaded_idmap);
+ std::unique_ptr<Asset>&& idmap_asset,
+ std::unique_ptr<LoadedIdmap>&& loaded_idmap);
+
+ static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
+ package_property_t flags = 0U);
// Allows us to make it possible to call make_shared from inside the class but still keeps the
// ctor 'private' for all means and purposes.
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 70326b71da28..c36d9908032f 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -28,6 +28,7 @@ using ::android::base::unique_fd;
using ::com::android::basic::R;
using ::testing::Eq;
using ::testing::Ge;
+using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::SizeIs;
using ::testing::StrEq;
@@ -108,4 +109,26 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
}
+TEST(ApkAssetsTest, TakeAssetsProviderNotCrashing) {
+ // Make sure the apk assets object can survive taking its assets provider and doesn't crash
+ // the process.
+ {
+ auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ ASSERT_THAT(loaded_apk, NotNull());
+
+ auto provider = std::move(*loaded_apk).TakeAssetsProvider();
+ ASSERT_THAT(provider, NotNull());
+ }
+ // If this test doesn't crash by this point we're all good.
+}
+
+TEST(ApkAssetsTest, AssetsProviderNotMovedOnError) {
+ auto assets_provider
+ = ZipAssetsProvider::Create(GetTestDataPath() + "/bad/bad.apk", 0);
+ ASSERT_THAT(assets_provider, NotNull());
+ auto loaded_apk = ApkAssets::Load(std::move(assets_provider));
+ ASSERT_THAT(loaded_apk, IsNull());
+ ASSERT_THAT(assets_provider, NotNull());
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/data/bad/bad.apk b/libs/androidfw/tests/data/bad/bad.apk
new file mode 100644
index 000000000000..3226bcd52e99
--- /dev/null
+++ b/libs/androidfw/tests/data/bad/bad.apk
Binary files differ
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index de402095e195..139ccfd22b0e 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,6 +16,7 @@ package com.android.extensions.appfunctions {
field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
field public static final int ERROR_DENIED = 1000; // 0x3e8
field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2
field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
index 2540236f2ce5..0c521690b165 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -71,6 +71,13 @@ public final class AppFunctionException extends Exception {
public static final int ERROR_CANCELLED = 2001;
/**
+ * The operation was disallowed by enterprise policy.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002;
+
+ /**
* An unknown error occurred while processing the call in the AppFunctionService.
*
* <p>This error is thrown when the service is connected in the remote application but an
@@ -189,7 +196,8 @@ public final class AppFunctionException extends Exception {
ERROR_SYSTEM_ERROR,
ERROR_INVALID_ARGUMENT,
ERROR_DISABLED,
- ERROR_CANCELLED
+ ERROR_CANCELLED,
+ ERROR_ENTERPRISE_POLICY_DISALLOWED
})
@Retention(RetentionPolicy.SOURCE)
public @interface ErrorCode {}
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index fa27af671be6..e497ea1f3cb4 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "core_graphics"
description: "API for AGSL authored runtime color filters and blenders"
bug: "358126864"
+ is_exported: true
}
flag {
@@ -44,6 +45,7 @@ flag {
namespace: "accessibility"
description: "Draw a solid rectangle background behind text instead of a stroke outline"
bug: "186567103"
+ is_exported: true
}
flag {
@@ -96,6 +98,7 @@ flag {
namespace: "core_graphics"
description: "Add canvas#drawRegion API"
bug: "318612129"
+ is_exported: true
}
flag {
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e5fb75575ac3..7b45070af312 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -48,7 +48,14 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
minikinPaint.localeListId = paint->getMinikinLocaleListId();
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
- minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+ if (!resolvedFace->fIsVariationInstance) {
+ // This is an optimization for direct private API use typically done by System UI.
+ // In the public API surface, if Typeface is already configured for variation instance
+ // (Target SDK <= 35) the font variation settings of Paint is not set.
+ // On the other hand, if Typeface is not configured so (Target SDK >= 36), the font
+ // variation settings are configured dynamically.
+ minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+ }
minikinPaint.verticalText = paint->isVerticalText();
const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 2d812d675fdc..4dfe05377a48 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -76,6 +76,7 @@ Typeface* Typeface::createRelative(Typeface* src, Typeface::Style style) {
result->fBaseWeight = resolvedFace->fBaseWeight;
result->fAPIStyle = style;
result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
+ result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
}
return result;
}
@@ -88,6 +89,7 @@ Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) {
result->fBaseWeight = resolvedFace->fBaseWeight;
result->fAPIStyle = computeAPIStyle(weight, italic);
result->fStyle = computeMinikinStyle(weight, italic);
+ result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
}
return result;
}
@@ -109,6 +111,7 @@ Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
result->fBaseWeight = resolvedFace->fBaseWeight;
result->fAPIStyle = resolvedFace->fAPIStyle;
result->fStyle = resolvedFace->fStyle;
+ result->fIsVariationInstance = true;
}
return result;
}
@@ -121,6 +124,7 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
result->fBaseWeight = weight;
result->fAPIStyle = resolvedFace->fAPIStyle;
result->fStyle = computeRelativeStyle(weight, result->fAPIStyle);
+ result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
}
return result;
}
@@ -170,6 +174,7 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font
result->fBaseWeight = weight;
result->fAPIStyle = computeAPIStyle(weight, italic);
result->fStyle = computeMinikinStyle(weight, italic);
+ result->fIsVariationInstance = false;
return result;
}
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 2c96c1ad80fe..97d1bf4ef011 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -44,6 +44,9 @@ public:
// base weight in CSS-style units, 1..1000
int fBaseWeight;
+ // True if the Typeface is already created for variation settings.
+ bool fIsVariationInstance;
+
static const Typeface* resolveDefault(const Typeface* src);
// The following three functions create new Typeface from an existing Typeface with a different
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 5f693462af91..d1782b285b34 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -68,7 +68,7 @@ static void releaseLayout(jlong ptr) {
static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count,
int contextStart, int contextCount, minikin::Bidi bidiFlags,
const Paint& paint, const Typeface* typeface) {
-
+ const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface);
minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface,
@@ -103,8 +103,16 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
fontId = it->second; // We've seen it.
} else {
fontId = fonts.size(); // This is new to us. Create new one.
- std::shared_ptr<minikin::Font> font = std::make_shared<minikin::Font>(
- fakedFont.font, fakedFont.fakery.variationSettings());
+ std::shared_ptr<minikin::Font> font;
+ if (resolvedFace->fIsVariationInstance) {
+ // The optimization for target SDK 35 or before because the variation instance
+ // is already created and no runtime variation resolution happens on such
+ // environment.
+ font = fakedFont.font;
+ } else {
+ font = std::make_shared<minikin::Font>(fakedFont.font,
+ fakedFont.fakery.variationSettings());
+ }
fonts.push_back(reinterpret_cast<jlong>(new FontWrapper(std::move(font))));
fakedToFontIds.insert(std::make_pair(fakedFont, fontId));
}
@@ -217,8 +225,8 @@ static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong
constexpr float NO_OVERRIDE = -1;
-float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) {
- for (const minikin::FontVariation& fv : fakery.variationSettings()) {
+float findValueFromVariationSettings(const minikin::VariationSettings& axes, minikin::AxisTag tag) {
+ for (const minikin::FontVariation& fv : axes) {
if (fv.axisTag == tag) {
return fv.value;
}
@@ -230,8 +238,8 @@ float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin:
static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
if (text_feature::typeface_redesign_readonly()) {
- float value =
- findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
+ float value = findValueFromVariationSettings(layout->layout.typeface(i)->GetAxes(),
+ minikin::TAG_wght);
return std::isnan(value) ? NO_OVERRIDE : value;
} else {
return layout->layout.getFakery(i).wghtAdjustment();
@@ -242,8 +250,8 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon
static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
if (text_feature::typeface_redesign_readonly()) {
- float value =
- findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
+ float value = findValueFromVariationSettings(layout->layout.typeface(i)->GetAxes(),
+ minikin::TAG_ital);
return std::isnan(value) ? NO_OVERRIDE : value;
} else {
return layout->layout.getFakery(i).italAdjustment();
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index d993b8715260..65663d065b8e 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -28,12 +28,14 @@
#define INDENT " "
#define INDENT2 " "
+namespace android {
+
namespace {
+
// Time to spend fading out the pointer completely.
const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-} // namespace
-namespace android {
+} // namespace
// --- MouseCursorController ---
@@ -47,8 +49,7 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context)
mLocked.lastFrameUpdatedTime = 0;
mLocked.pointerFadeDirection = 0;
- mLocked.pointerX = 0;
- mLocked.pointerY = 0;
+ mLocked.pointerPosition = {0, 0};
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
mLocked.pointerSprite = mContext.getSpriteController().createSprite();
mLocked.updatePointerIcon = false;
@@ -64,50 +65,60 @@ MouseCursorController::~MouseCursorController() {
mLocked.pointerSprite.clear();
}
-void MouseCursorController::move(float deltaX, float deltaY) {
+vec2 MouseCursorController::move(vec2 delta) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
#endif
- if (deltaX == 0.0f && deltaY == 0.0f) {
- return;
+ if (delta.x == 0.0f && delta.y == 0.0f) {
+ return {0, 0};
}
+ // When transition occurs, the MouseCursorController object may or may not be deleted, depending
+ // if there's another display on the other side of the transition. At this point we still need
+ // to move the cursor to the boundary.
std::scoped_lock lock(mLock);
-
- setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+ const vec2 targetPosition = mLocked.pointerPosition + delta;
+ setPositionLocked(targetPosition);
+ // The amount of the delta that was not consumed as a result of the cursor
+ // hitting the edge of the display.
+ return targetPosition - mLocked.pointerPosition;
}
-void MouseCursorController::setPosition(float x, float y) {
+void MouseCursorController::setPosition(vec2 position) {
#if DEBUG_MOUSE_CURSOR_UPDATES
ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
#endif
std::scoped_lock lock(mLock);
- setPositionLocked(x, y);
+ setPositionLocked(position);
}
-void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- const auto& v = mLocked.viewport;
- if (!v.isValid()) return;
-
+FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) {
// The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
// the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
// close to the outside edge.
- const FloatRect bounds{
+ return FloatRect{
static_cast<float>(mLocked.viewport.logicalLeft),
static_cast<float>(mLocked.viewport.logicalTop),
static_cast<float>(mLocked.viewport.logicalRight - 1),
static_cast<float>(mLocked.viewport.logicalBottom - 1),
};
- mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
- mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
+}
+
+void MouseCursorController::setPositionLocked(vec2 position) REQUIRES(mLock) {
+ const auto& v = mLocked.viewport;
+ if (!v.isValid()) return;
+
+ const FloatRect bounds = getBoundsLocked();
+ mLocked.pointerPosition.x = std::max(bounds.left, std::min(bounds.right, position.x));
+ mLocked.pointerPosition.y = std::max(bounds.top, std::min(bounds.bottom, position.y));
updatePointerLocked();
}
-FloatPoint MouseCursorController::getPosition() const {
+vec2 MouseCursorController::getPosition() const {
std::scoped_lock lock(mLock);
- return {mLocked.pointerX, mLocked.pointerY};
+ return mLocked.pointerPosition;
}
ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
@@ -209,20 +220,21 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
if (viewport.isValid()) {
// Use integer coordinates as the starting point for the cursor location.
// We usually expect display sizes to be even numbers, so the flooring is precautionary.
- mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2);
- mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2);
+ mLocked.pointerPosition.x =
+ std::floor((viewport.logicalLeft + viewport.logicalRight) / 2);
+ mLocked.pointerPosition.y =
+ std::floor((viewport.logicalTop + viewport.logicalBottom) / 2);
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
- mLocked.pointerX = 0;
- mLocked.pointerY = 0;
+ mLocked.pointerPosition = {0, 0};
}
} else if (oldViewport.orientation != viewport.orientation) {
// Apply offsets to convert from the pixel top-left corner position to the pixel center.
// This creates an invariant frame of reference that we can easily rotate when
// taking into account that the pointer may be located at fractional pixel offsets.
- float x = mLocked.pointerX + 0.5f;
- float y = mLocked.pointerY + 0.5f;
+ float x = mLocked.pointerPosition.x + 0.5f;
+ float y = mLocked.pointerPosition.y + 0.5f;
float temp;
// Undo the previous rotation.
@@ -267,8 +279,8 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
// Apply offsets to convert from the pixel center to the pixel top-left corner position
// and save the results.
- mLocked.pointerX = x - 0.5f;
- mLocked.pointerY = y - 0.5f;
+ mLocked.pointerPosition.x = x - 0.5f;
+ mLocked.pointerPosition.y = y - 0.5f;
}
updatePointerLocked();
@@ -354,7 +366,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
spriteController.openTransaction();
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
- mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+ mLocked.pointerSprite->setPosition(mLocked.pointerPosition.x, mLocked.pointerPosition.y);
mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 12b31a8c531a..7c674b53d620 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -20,9 +20,6 @@
#include <gui/DisplayEventReceiver.h>
#include <input/DisplayViewport.h>
#include <input/Input.h>
-#include <utils/BitSet.h>
-#include <utils/Looper.h>
-#include <utils/RefBase.h>
#include <functional>
#include <map>
@@ -43,9 +40,10 @@ public:
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- void move(float deltaX, float deltaY);
- void setPosition(float x, float y);
- FloatPoint getPosition() const;
+ // Move the pointer and return unconsumed delta
+ vec2 move(vec2 delta);
+ void setPosition(vec2 position);
+ vec2 getPosition() const;
ui::LogicalDisplayId getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
@@ -83,8 +81,7 @@ private:
nsecs_t lastFrameUpdatedTime;
int32_t pointerFadeDirection;
- float pointerX;
- float pointerY;
+ vec2 pointerPosition;
float pointerAlpha;
sp<Sprite> pointerSprite;
SpriteIcon pointerIcon;
@@ -103,7 +100,7 @@ private:
} mLocked GUARDED_BY(mLock);
- void setPositionLocked(float x, float y);
+ void setPositionLocked(vec2 position);
void updatePointerLocked();
@@ -113,6 +110,7 @@ private:
bool doFadingAnimationLocked(nsecs_t timestamp);
void startAnimationLocked();
+ FloatRect getBoundsLocked();
};
} // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 78d7d3a7051b..0b81211ee666 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,15 +138,16 @@ std::mutex& PointerController::getLock() const {
return mDisplayInfoListener->mLock;
}
-void PointerController::move(float deltaX, float deltaY) {
+vec2 PointerController::move(float deltaX, float deltaY) {
const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
- vec2 transformed;
+ ui::Transform transform;
{
std::scoped_lock lock(getLock());
- const auto& transform = getTransformForDisplayLocked(displayId);
- transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+ transform = getTransformForDisplayLocked(displayId);
}
- mCursorController.move(transformed.x, transformed.y);
+
+ const vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+ return transformWithoutTranslation(transform.inverse(), mCursorController.move(transformed));
}
void PointerController::setPosition(float x, float y) {
@@ -157,16 +158,15 @@ void PointerController::setPosition(float x, float y) {
const auto& transform = getTransformForDisplayLocked(displayId);
transformed = transform.transform(x, y);
}
- mCursorController.setPosition(transformed.x, transformed.y);
+ mCursorController.setPosition(transformed);
}
-FloatPoint PointerController::getPosition() const {
+vec2 PointerController::getPosition() const {
const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
- const auto& transform = getTransformForDisplayLocked(displayId);
- return FloatPoint{transform.inverse().transform(p.x, p.y)};
+ return getTransformForDisplayLocked(displayId).inverse().transform(p.x, p.y);
}
}
@@ -295,6 +295,11 @@ void PointerController::clearSkipScreenshotFlags() {
mCursorController.setSkipScreenshot(false);
}
+ui::Transform PointerController::getDisplayTransform() const {
+ std::scoped_lock lock(getLock());
+ return getTransformForDisplayLocked(mLocked.pointerDisplayId);
+}
+
void PointerController::doInactivityTimeout() {
fade(Transition::GRADUAL);
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index ee8d1211341f..afd7215c7fba 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,9 +51,9 @@ public:
~PointerController() override;
- void move(float deltaX, float deltaY) override;
+ vec2 move(float deltaX, float deltaY) override;
void setPosition(float x, float y) override;
- FloatPoint getPosition() const override;
+ vec2 getPosition() const override;
ui::LogicalDisplayId getDisplayId() const override;
void fade(Transition transition) override;
void unfade(Transition transition) override;
@@ -67,6 +67,7 @@ public:
void setCustomPointerIcon(const SpriteIcon& icon) override;
void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
void clearSkipScreenshotFlags() override;
+ ui::Transform getDisplayTransform() const override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -165,13 +166,13 @@ public:
~TouchPointerController() override;
- void move(float, float) override {
+ vec2 move(float, float) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void setPosition(float, float) override {
LOG_ALWAYS_FATAL("Should not be called");
}
- FloatPoint getPosition() const override {
+ vec2 getPosition() const override {
LOG_ALWAYS_FATAL("Should not be called");
}
ui::LogicalDisplayId getDisplayId() const override {
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 5b00fca4d857..80c934a9bd95 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -40,6 +40,8 @@ enum TestCursorType {
CURSOR_TYPE_CUSTOM = -1,
};
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
using ::testing::AllOf;
using ::testing::Field;
using ::testing::NiceMock;
@@ -399,6 +401,135 @@ INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest,
testing::Values(PointerControllerInterface::ControllerType::MOUSE,
PointerControllerInterface::ControllerType::STYLUS));
+class MousePointerControllerTest : public PointerControllerTest {
+protected:
+ MousePointerControllerTest() {
+ sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+ EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+ // create a mouse pointer controller
+ mPointerController =
+ PointerController::create(mPolicy, mLooper, *mSpriteController,
+ PointerControllerInterface::ControllerType::MOUSE);
+
+ // set display viewport
+ DisplayViewport viewport;
+ viewport.displayId = ui::LogicalDisplayId::DEFAULT;
+ viewport.logicalRight = 5;
+ viewport.logicalBottom = 5;
+ viewport.physicalRight = 5;
+ viewport.physicalBottom = 5;
+ viewport.deviceWidth = 5;
+ viewport.deviceHeight = 5;
+ mPointerController->setDisplayViewport(viewport);
+ }
+};
+
+struct MousePointerControllerTestParam {
+ vec2 startPosition;
+ vec2 delta;
+ vec2 endPosition;
+ vec2 unconsumedDelta;
+};
+
+class PointerControllerViewportTransitionTest
+ : public MousePointerControllerTest,
+ public testing::WithParamInterface<MousePointerControllerTestParam> {};
+
+TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) {
+ const auto& params = GetParam();
+
+ mPointerController->setPosition(params.startPosition.x, params.startPosition.y);
+ auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y);
+
+ auto position = mPointerController->getPosition();
+ EXPECT_NEAR(position.x, params.endPosition.x, EPSILON);
+ EXPECT_NEAR(position.y, params.endPosition.y, EPSILON);
+ EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON);
+ EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON);
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest,
+ PointerControllerViewportTransitionTest,
+ testing::Values(
+ // no transition
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {2.0f, 2.0f},
+ {4.0f, 4.0f},
+ {0.0f, 0.0f}},
+ // right boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 0.0f},
+ {4.0f, 2.0f},
+ {1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, -1.0f},
+ {4.0f, 1.0f},
+ {1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 1.0f},
+ {4.0f, 3.0f},
+ {1.0f, 0.0f}},
+ // left boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 0.0f},
+ {0.0f, 2.0f},
+ {-1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, -1.0f},
+ {0.0f, 1.0f},
+ {-1.0f, 0.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 1.0f},
+ {0.0f, 3.0f},
+ {-1.0f, 0.0f}},
+ // bottom boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {0.0f, 3.0f},
+ {2.0f, 4.0f},
+ {0.0f, 1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-1.0f, 3.0f},
+ {1.0f, 4.0f},
+ {0.0f, 1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {1.0f, 3.0f},
+ {3.0f, 4.0f},
+ {0.0f, 1.0f}},
+ // top boundary
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {0.0f, -3.0f},
+ {2.0f, 0.0f},
+ {0.0f, -1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-1.0f, -3.0f},
+ {1.0f, 0.0f},
+ {0.0f, -1.0f}},
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {1.0f, -3.0f},
+ {3.0f, 0.0f},
+ {0.0f, -1.0f}},
+ // top-left corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, -3.0f},
+ {0.0f, 0.0f},
+ {-1.0f, -1.0f}},
+ // top-right corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, -3.0f},
+ {4.0f, 0.0f},
+ {1.0f, -1.0f}},
+ // bottom-right corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {3.0f, 3.0f},
+ {4.0f, 4.0f},
+ {1.0f, 1.0f}},
+ // bottom-left corner
+ MousePointerControllerTestParam{{2.0f, 2.0f},
+ {-3.0f, 3.0f},
+ {0.0f, 4.0f},
+ {-1.0f, 1.0f}}));
+
class PointerControllerWindowInfoListenerTest : public Test {};
TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index cf3f74085d66..9478e350de57 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -642,6 +642,14 @@ package android.location.provider {
method public void onFlushComplete();
}
+ @FlaggedApi("android.location.flags.population_density_provider") public abstract class PopulationDensityProviderBase {
+ ctor public PopulationDensityProviderBase(@NonNull android.content.Context, @NonNull String);
+ method @Nullable public final android.os.IBinder getBinder();
+ method public abstract void onGetCoarsenedS2Cells(double, double, @IntRange(from=0) int, @NonNull android.os.OutcomeReceiver<long[],java.lang.Throwable>);
+ method public abstract void onGetDefaultCoarseningLevel(@NonNull android.os.OutcomeReceiver<java.lang.Integer,java.lang.Throwable>);
+ field public static final String ACTION_POPULATION_DENSITY_PROVIDER = "com.android.location.service.PopulationDensityProvider";
+ }
+
public final class ProviderRequest implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0) public long getIntervalMillis();
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 24e1d32164d0..5395206155b3 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "location"
description: "Deprecates LocationManager ProviderChanged APIs"
bug: "361811782"
+ is_exported: true
}
flag {
@@ -27,6 +28,7 @@ flag {
namespace: "location"
description: "Flag for new Geocoder APIs"
bug: "229872126"
+ is_exported: true
}
flag {
@@ -56,6 +58,7 @@ flag {
namespace: "location"
description: "Flag for making geoid heights available via the Altitude HAL"
bug: "304375846"
+ is_exported: true
}
flag {
@@ -63,6 +66,7 @@ flag {
namespace: "location"
description: "Flag for GNSS API for NavIC L1"
bug: "302199306"
+ is_exported: true
}
flag {
@@ -70,6 +74,7 @@ flag {
namespace: "location"
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
+ is_exported: true
}
flag {
@@ -129,6 +134,7 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
diff --git a/location/java/android/location/provider/IPopulationDensityProvider.aidl b/location/java/android/location/provider/IPopulationDensityProvider.aidl
new file mode 100644
index 000000000000..41fe5006983d
--- /dev/null
+++ b/location/java/android/location/provider/IPopulationDensityProvider.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.location.provider;
+
+import android.os.Bundle;
+
+import android.location.Location;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+
+/**
+ * Binder interface for services that implement a population density provider. Do not implement this
+ * directly, extend {@link PopulationDensityProviderBase} instead.
+ * @hide
+ */
+oneway interface IPopulationDensityProvider {
+ /**
+ * Gets the default S2 level to be used to coarsen any location, in case a more precise answer
+ * from the method below can't be obtained.
+ */
+ void getDefaultCoarseningLevel(in IS2LevelCallback callback);
+
+ /**
+ * Requests a list of IDs of the S2 cells to be used to coarsen a location. The answer should
+ * contain at least one S2 cell, which should contain the requested location. Its level
+ * represents the population density. Optionally, if numAdditionalCells is greater than 0,
+ * additional nearby cells can be also returned, to assist in coarsening nearby locations.
+ */
+ void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+ int numAdditionalCells, in IS2CellIdsCallback callback);
+}
diff --git a/location/java/android/location/provider/IS2CellIdsCallback.aidl b/location/java/android/location/provider/IS2CellIdsCallback.aidl
new file mode 100644
index 000000000000..f583045ebb26
--- /dev/null
+++ b/location/java/android/location/provider/IS2CellIdsCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 cell IDs callbacks.
+ * @hide
+ */
+oneway interface IS2CellIdsCallback {
+
+ /**
+ * Called with the resulting list of S2 cell IDs. The first cell is expected to contain
+ * the requested latitude/longitude. Its level represent the population density. Optionally,
+ * the list can also contain additional nearby cells.
+ */
+ void onResult(in long[] s2CellIds);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/IS2LevelCallback.aidl b/location/java/android/location/provider/IS2LevelCallback.aidl
new file mode 100644
index 000000000000..49f96ef7e3e2
--- /dev/null
+++ b/location/java/android/location/provider/IS2LevelCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 level callback.
+ * @hide
+ */
+oneway interface IS2LevelCallback {
+ /**
+ * Called with the resulting default S2 level for coarsening a location, in case a better
+ * answer cannot be obtained for a latitude/longitude.
+ */
+ void onResult(int s2Level);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/PopulationDensityProviderBase.java b/location/java/android/location/provider/PopulationDensityProviderBase.java
new file mode 100644
index 000000000000..0177cf8694df
--- /dev/null
+++ b/location/java/android/location/provider/PopulationDensityProviderBase.java
@@ -0,0 +1,194 @@
+/*
+ * 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.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A provider for population density.
+ * The population density is defined as the S2 level at which the S2 cell around the latitude /
+ * longitude contains at least a thousand people.
+ * It exposes two methods: one about providing population density around a latitude / longitude,
+ * and one about providing a "default" population density to fall back to in case the first API
+ * can't be used or returns an error.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public abstract class PopulationDensityProviderBase {
+
+ final String mTag;
+ final @Nullable String mAttributionTag;
+ final IBinder mBinder;
+
+ /**
+ * The action the wrapping service should have in its intent filter to implement the
+ * PopulationDensity provider.
+ */
+ @SuppressLint("ActionValue")
+ public static final String ACTION_POPULATION_DENSITY_PROVIDER =
+ "com.android.location.service.PopulationDensityProvider";
+
+ public PopulationDensityProviderBase(@NonNull Context context, @NonNull String tag) {
+ mTag = tag;
+ mAttributionTag = context.getAttributionTag();
+ mBinder = new Service();
+ }
+
+ /**
+ * Returns the IBinder instance that should be returned from the
+ * {@link android.app.Service#onBind(Intent)} method of the wrapping service.
+ */
+ public final @Nullable IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Called upon receiving a new request for the default coarsening level.
+ * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+ * an error occurs, {@link OutcomeReceiver#onError} should be called.
+ * The callback is single-use, calling more than any one of these two methods throws an
+ * AssertionException.
+ *
+ * @param callback A single-use callback that either returns the coarsening level, or an error.
+ */
+ public abstract void onGetDefaultCoarseningLevel(@NonNull OutcomeReceiver<Integer, Throwable>
+ callback);
+
+ /**
+ * Called upon receiving a new request for population density at a specific latitude/longitude,
+ * expressed in degrees.
+ * The answer is at least one S2CellId corresponding to the coarsening level at the specified
+ * location. This must be the first element of the result array. Optionally, if
+ * numAdditionalCells is greater than zero, additional nearby S2CellIds can be returned. One use
+ * for the optional nearby cells is when the client has a local cache that needs to be filled
+ * with the local area around a certain latitude/longitude. The callback
+ * {@link OutcomeReceiver#onResult} should be called with the result; or, in case an error
+ * occurs, {@link OutcomeReceiver#onError} should be called. The callback is single-use, calling
+ * more than any one of these two methods throws an AssertionException.
+ *
+ * @param callback A single-use callback that either returns S2CellIds, or an error.
+ */
+ public abstract void onGetCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+ @IntRange(from = 0) int numAdditionalCells,
+ @NonNull OutcomeReceiver<long[], Throwable> callback);
+
+ private final class Service extends IPopulationDensityProvider.Stub {
+ @Override
+ public void getDefaultCoarseningLevel(@NonNull IS2LevelCallback callback) {
+ try {
+ onGetDefaultCoarseningLevel(new SingleUseS2LevelCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+
+ @Override
+ public void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+ int numAdditionalCells, @NonNull IS2CellIdsCallback callback) {
+ try {
+ onGetCoarsenedS2Cells(latitudeDegrees, longitudeDegrees, numAdditionalCells,
+ new SingleUseS2CellIdsCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+ }
+
+ private static class SingleUseS2LevelCallback implements OutcomeReceiver<Integer, Throwable> {
+
+ private final AtomicReference<IS2LevelCallback> mCallback;
+
+ SingleUseS2LevelCallback(IS2LevelCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(Integer level) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(level);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private static class SingleUseS2CellIdsCallback implements OutcomeReceiver<long[], Throwable> {
+
+ private final AtomicReference<IS2CellIdsCallback> mCallback;
+
+ SingleUseS2CellIdsCallback(IS2CellIdsCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(long[] s2CellIds) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(s2CellIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index b41f40f412d2..5689df0784f0 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -548,8 +548,45 @@ public final class AudioDeviceInfo {
return counts;
}
+ /** @hide */
+ @IntDef(flag = true, prefix = "AudioFormat.CHANNEL_OUT_", value = {
+ AudioFormat.CHANNEL_INVALID,
+ AudioFormat.CHANNEL_OUT_DEFAULT,
+ AudioFormat.CHANNEL_OUT_FRONT_LEFT,
+ AudioFormat.CHANNEL_OUT_FRONT_RIGHT,
+ AudioFormat.CHANNEL_OUT_FRONT_CENTER,
+ AudioFormat.CHANNEL_OUT_LOW_FREQUENCY,
+ AudioFormat.CHANNEL_OUT_BACK_LEFT,
+ AudioFormat.CHANNEL_OUT_BACK_RIGHT,
+ AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER,
+ AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+ AudioFormat.CHANNEL_OUT_BACK_CENTER,
+ AudioFormat.CHANNEL_OUT_SIDE_LEFT,
+ AudioFormat.CHANNEL_OUT_SIDE_RIGHT,
+ AudioFormat.CHANNEL_OUT_TOP_CENTER,
+ AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT,
+ AudioFormat.CHANNEL_OUT_TOP_FRONT_CENTER,
+ AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT,
+ AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT,
+ AudioFormat.CHANNEL_OUT_TOP_BACK_CENTER,
+ AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT,
+ AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT,
+ AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT,
+ AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT,
+ AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER,
+ AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+ AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2,
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT,
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi(FLAG_SPEAKER_LAYOUT_API)
+ public @interface SpeakerLayoutChannelMask {}
+
/**
- * @return A ChannelMask representing the physical output speaker layout of the device.
+ * @return A ChannelMask representing the speaker layout of a TYPE_BUILTIN_SPEAKER device.
+ *
+ * Valid only for speakers built-in to the device.
*
* The layout channel mask only indicates which speaker channels are present, the
* physical layout of the speakers should be informed by a standard for multi-channel
@@ -557,6 +594,7 @@ public final class AudioDeviceInfo {
* @see AudioFormat
*/
@FlaggedApi(FLAG_SPEAKER_LAYOUT_API)
+ @SpeakerLayoutChannelMask
public int getSpeakerLayoutChannelMask() {
return mPort.speakerLayoutChannelMask();
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 0da8371bc824..8bc66a048d27 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -17,6 +17,7 @@
package android.media;
import static android.media.audio.Flags.FLAG_DOLBY_AC4_LEVEL4_ENCODING_API;
+import static android.media.audio.Flags.FLAG_IAMF_DEFINITIONS_API;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -382,6 +383,103 @@ public final class AudioFormat implements Parcelable {
@FlaggedApi(FLAG_DOLBY_AC4_LEVEL4_ENCODING_API)
public static final int ENCODING_AC4_L4 = 32;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-simple">simple profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in OPUS.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_SIMPLE_PROFILE_OPUS = 33;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-simple">simple profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in AAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_SIMPLE_PROFILE_AAC = 34;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-simple">simple profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in FLAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_SIMPLE_PROFILE_FLAC = 35;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-simple">simple profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in PCM.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_SIMPLE_PROFILE_PCM = 36;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base">base profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in OPUS.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_PROFILE_OPUS = 37;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base">base profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in AAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_PROFILE_AAC = 38;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base">base profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in FLAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_PROFILE_FLAC = 39;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base">base profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in PCM.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_PROFILE_PCM = 40;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base-enhanced">base-enhanced profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in OPUS.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS = 41;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base-enhanced">base-enhanced profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in AAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC = 42;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base-enhanced">base-enhanced profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in FLAC.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC = 43;
+ /**
+ * Audio data format: IAMF using the
+ * <a href="https://aomediacodec.github.io/iamf/#profiles-base-enhanced">base-enhanced profile</a>
+ * with audio streams <a href="https://aomediacodec.github.io/iamf/#codec_id">encoded</a>
+ * in PCM.
+ */
+ @FlaggedApi(FLAG_IAMF_DEFINITIONS_API)
+ public static final int ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM = 44;
+
/** @hide */
public static String toLogFriendlyEncoding(int enc) {
switch(enc) {
@@ -449,6 +547,30 @@ public final class AudioFormat implements Parcelable {
return "ENCODING_DTS_UHD_P2";
case ENCODING_DSD:
return "ENCODING_DSD";
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ return "ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC";
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ return "ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC";
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ return "ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS";
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ return "ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM";
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ return "ENCODING_IAMF_BASE_PROFILE_AAC";
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ return "ENCODING_IAMF_BASE_PROFILE_FLAC";
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ return "ENCODING_IAMF_BASE_PROFILE_OPUS";
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ return "ENCODING_IAMF_BASE_PROFILE_PCM";
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ return "ENCODING_IAMF_SIMPLE_PROFILE_AAC";
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ return "ENCODING_IAMF_SIMPLE_PROFILE_FLAC";
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ return "ENCODING_IAMF_SIMPLE_PROFILE_OPUS";
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
+ return "ENCODING_IAMF_SIMPLE_PROFILE_PCM";
default :
return "invalid encoding " + enc;
}
@@ -714,7 +836,7 @@ public final class AudioFormat implements Parcelable {
/**
* @hide
* Return a channel mask ready to be used by native code
- * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+ * @param javaMask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
* @return a native channel mask
*/
public static int convertChannelOutMaskToNativeMask(int javaMask) {
@@ -724,13 +846,98 @@ public final class AudioFormat implements Parcelable {
/**
* @hide
* Return a java output channel mask
- * @param mask a native channel mask
+ * @param nativeMask a native channel mask
* @return a combination of the CHANNEL_OUT_* definitions
*/
public static int convertNativeChannelMaskToOutMask(int nativeMask) {
return (nativeMask << 2);
}
+ /**
+ * @hide
+ * Return a human-readable string from a java channel mask
+ * @param javaMask a bit field of CHANNEL_OUT_* values
+ * @return a string in the "mono", "stereo", "5.1" style, or the hex version when not a standard
+ * mask.
+ */
+ public static String javaChannelOutMaskToString(int javaMask) {
+ // save haptics info for end of string
+ int haptics = javaMask & (CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B);
+ // continue without looking at haptic channels
+ javaMask &= ~(CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B);
+ StringBuilder result = new StringBuilder("");
+ switch (javaMask) {
+ case CHANNEL_OUT_MONO:
+ result.append("mono");
+ break;
+ case CHANNEL_OUT_STEREO:
+ result.append("stereo");
+ break;
+ case CHANNEL_OUT_QUAD:
+ result.append("quad");
+ break;
+ case CHANNEL_OUT_QUAD_SIDE:
+ result.append("quad side");
+ break;
+ case CHANNEL_OUT_SURROUND:
+ result.append("4.0");
+ break;
+ case CHANNEL_OUT_5POINT1:
+ result.append("5.1");
+ break;
+ case CHANNEL_OUT_6POINT1:
+ result.append("6.1");
+ break;
+ case CHANNEL_OUT_5POINT1_SIDE:
+ result.append("5.1 side");
+ break;
+ case CHANNEL_OUT_7POINT1:
+ result.append("7.1 (5 fronts)");
+ break;
+ case CHANNEL_OUT_7POINT1_SURROUND:
+ result.append("7.1");
+ break;
+ case CHANNEL_OUT_5POINT1POINT2:
+ result.append("5.1.2");
+ break;
+ case CHANNEL_OUT_5POINT1POINT4:
+ result.append("5.1.4");
+ break;
+ case CHANNEL_OUT_7POINT1POINT2:
+ result.append("7.1.2");
+ break;
+ case CHANNEL_OUT_7POINT1POINT4:
+ result.append("7.1.4");
+ break;
+ case CHANNEL_OUT_9POINT1POINT4:
+ result.append("9.1.4");
+ break;
+ case CHANNEL_OUT_9POINT1POINT6:
+ result.append("9.1.6");
+ break;
+ case CHANNEL_OUT_13POINT_360RA:
+ result.append("360RA 13ch");
+ break;
+ case CHANNEL_OUT_22POINT2:
+ result.append("22.2");
+ break;
+ default:
+ result.append("0x").append(Integer.toHexString(javaMask));
+ break;
+ }
+ if ((haptics & (CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B)) != 0) {
+ result.append("(+haptic ");
+ if ((haptics & CHANNEL_OUT_HAPTIC_A) == CHANNEL_OUT_HAPTIC_A) {
+ result.append("A");
+ }
+ if ((haptics & CHANNEL_OUT_HAPTIC_B) == CHANNEL_OUT_HAPTIC_B) {
+ result.append("B");
+ }
+ result.append(")");
+ }
+ return result.toString();
+ }
+
public static final int CHANNEL_IN_DEFAULT = 1;
// These directly match native
public static final int CHANNEL_IN_LEFT = 0x4;
@@ -846,6 +1053,18 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
case ENCODING_DSD:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
return true;
default:
return false;
@@ -887,6 +1106,18 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
case ENCODING_DSD:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
return true;
default:
return false;
@@ -930,6 +1161,18 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM: // PCM but inside compressed stream
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_PROFILE_PCM: // PCM but inside compressed stream
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM: // PCM but inside compressed stream
return false;
case ENCODING_INVALID:
default:
@@ -973,6 +1216,18 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
return false;
case ENCODING_INVALID:
default:
@@ -1265,6 +1520,18 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
case ENCODING_DSD:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM:
+ case ENCODING_IAMF_BASE_PROFILE_AAC:
+ case ENCODING_IAMF_BASE_PROFILE_FLAC:
+ case ENCODING_IAMF_BASE_PROFILE_OPUS:
+ case ENCODING_IAMF_BASE_PROFILE_PCM:
+ case ENCODING_IAMF_SIMPLE_PROFILE_AAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_FLAC:
+ case ENCODING_IAMF_SIMPLE_PROFILE_OPUS:
+ case ENCODING_IAMF_SIMPLE_PROFILE_PCM:
mEncoding = encoding;
break;
case ENCODING_INVALID:
@@ -1495,7 +1762,19 @@ public final class AudioFormat implements Parcelable {
ENCODING_DRA,
ENCODING_DTS_HD_MA,
ENCODING_DTS_UHD_P2,
- ENCODING_DSD }
+ ENCODING_DSD,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM,
+ ENCODING_IAMF_BASE_PROFILE_AAC,
+ ENCODING_IAMF_BASE_PROFILE_FLAC,
+ ENCODING_IAMF_BASE_PROFILE_OPUS,
+ ENCODING_IAMF_BASE_PROFILE_PCM,
+ ENCODING_IAMF_SIMPLE_PROFILE_AAC,
+ ENCODING_IAMF_SIMPLE_PROFILE_FLAC,
+ ENCODING_IAMF_SIMPLE_PROFILE_OPUS,
+ ENCODING_IAMF_SIMPLE_PROFILE_PCM }
)
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
@@ -1534,7 +1813,19 @@ public final class AudioFormat implements Parcelable {
ENCODING_DRA,
ENCODING_DTS_HD_MA,
ENCODING_DTS_UHD_P2,
- ENCODING_DSD }
+ ENCODING_DSD,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS,
+ ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM,
+ ENCODING_IAMF_BASE_PROFILE_AAC,
+ ENCODING_IAMF_BASE_PROFILE_FLAC,
+ ENCODING_IAMF_BASE_PROFILE_OPUS,
+ ENCODING_IAMF_BASE_PROFILE_PCM,
+ ENCODING_IAMF_SIMPLE_PROFILE_AAC,
+ ENCODING_IAMF_SIMPLE_PROFILE_FLAC,
+ ENCODING_IAMF_SIMPLE_PROFILE_OPUS,
+ ENCODING_IAMF_SIMPLE_PROFILE_PCM }
)
@Retention(RetentionPolicy.SOURCE)
public @interface EncodingCanBeInvalid {}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d0d91ba599f9..12d7f33a0d51 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -559,6 +559,30 @@ public class AudioSystem
return "AUDIO_FORMAT_MPEGH_SUB_LC_L3";
case /* AUDIO_FORMAT_MPEGH_SUB_LC_L4 */ 0x2C000024:
return "AUDIO_FORMAT_MPEGH_SUB_LC_L4";
+ case /* AUDIO_FORMAT_IAMF_SIMPLE_OPUS */ 0x34010001:
+ return "AUDIO_FORMAT_IAMF_SIMPLE_OPUS";
+ case /* AUDIO_FORMAT_IAMF_SIMPLE_AAC */ 0x34010002:
+ return "AUDIO_FORMAT_IAMF_SIMPLE_AAC";
+ case /* AUDIO_FORMAT_IAMF_SIMPLE_FLAC */ 0x34010004:
+ return "AUDIO_FORMAT_IAMF_SIMPLE_FLAC";
+ case /* AUDIO_FORMAT_IAMF_SIMPLE_PCM */ 0x34010008:
+ return "AUDIO_FORMAT_IAMF_SIMPLE_PCM";
+ case /* AUDIO_FORMAT_IAMF_BASE_OPUS */ 0x34020001:
+ return "AUDIO_FORMAT_IAMF_BASE_OPUS";
+ case /* AUDIO_FORMAT_IAMF_BASE_AAC */ 0x34020002:
+ return "AUDIO_FORMAT_IAMF_BASE_AAC";
+ case /* AUDIO_FORMAT_IAMF_BASE_FLAC */ 0x34020004:
+ return "AUDIO_FORMAT_IAMF_BASE_FLAC";
+ case /* AUDIO_FORMAT_IAMF_BASE_PCM */ 0x34020008:
+ return "AUDIO_FORMAT_IAMF_BASE_PCM";
+ case /* AUDIO_FORMAT_IAMF_BASE_ENHANCED_OPUS */ 0x34040001:
+ return "AUDIO_FORMAT_IAMF_BASE_ENHANCED_OPUS";
+ case /* AUDIO_FORMAT_IAMF_BASE_ENHANCED_AAC */ 0x34040002:
+ return "AUDIO_FORMAT_IAMF_BASE_ENHANCED_AAC";
+ case /* AUDIO_FORMAT_IAMF_BASE_ENHANCED_FLAC */ 0x34040004:
+ return "AUDIO_FORMAT_IAMF_BASE_ENHANCED_FLAC";
+ case /* AUDIO_FORMAT_IAMF_BASE_ENHANCED_PCM */ 0x34040008:
+ return "AUDIO_FORMAT_IAMF_BASE_ENHANCED_PCM";
default:
return "AUDIO_FORMAT_(" + audioFormat + ")";
}
diff --git a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
index 63c52a142fdd..8f030572e6a0 100644
--- a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
@@ -25,7 +25,6 @@ import android.os.Bundle;
* @hide
*/
oneway interface IMediaRoute2ProviderServiceCallback {
- // TODO: Change it to updateRoutes?
void notifyProviderUpdated(in MediaRoute2ProviderInfo providerInfo);
void notifySessionCreated(long requestId, in RoutingSessionInfo sessionInfo);
void notifySessionsUpdated(in List<RoutingSessionInfo> sessionInfo);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 1ecba31ce07f..3efb5f961dc8 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -1010,17 +1010,17 @@ public final class MediaCas implements AutoCloseable {
* scenario, when both resource holder and resource challenger have same processId and same
* priority.
*
- * @param resourceHolderRetain Set to {@code true} to allow the resource holder to retain
- * ownership, or false to allow the resource challenger to acquire the resource.
- * If not explicitly set, resourceHolderRetain is set to {@code false}.
+ *@param enabled Set to {@code true} to allow the resource holder to retain ownership,
+ * or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
* @hide
*/
@FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
@SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
- public void setResourceHolderRetain(boolean resourceHolderRetain) {
+ public void setResourceOwnershipRetention(boolean enabled) {
if (mTunerResourceManager != null) {
- mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
}
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 09022782e6c3..a3ad340f6ef4 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -21,8 +21,10 @@ import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE;
import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+import static com.android.media.flags.Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2;
import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES;
+import static com.android.media.flags.Flags.FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -47,6 +49,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Describes the properties of a route.
@@ -187,6 +190,7 @@ public final class MediaRoute2Info implements Parcelable {
* the device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BUILTIN_SPEAKER
*/
public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
@@ -194,6 +198,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a headset, which is the combination of a headphones and a microphone.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADSET
*/
public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
@@ -201,6 +206,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a pair of wired headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADPHONES
*/
public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
@@ -208,6 +214,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLUETOOTH_A2DP
*/
public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -215,6 +222,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI
*/
public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
@@ -222,6 +230,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is an Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_ARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
@@ -230,24 +239,34 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_EARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
/**
* Indicates the route is a digital line connection (for example S/PDIF).
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_DIGITAL
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_DIGITAL = AudioDeviceInfo.TYPE_LINE_DIGITAL;
/**
* Indicates the route is an analog line-level connection.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_ANALOG
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_ANALOG = AudioDeviceInfo.TYPE_LINE_ANALOG;
/**
* Indicates the route is using the auxiliary line-level connectors.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_AUX_LINE
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_AUX_LINE = AudioDeviceInfo.TYPE_AUX_LINE;
@@ -256,6 +275,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a USB audio device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_DEVICE
*/
public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -263,6 +283,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a USB audio device in accessory mode.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_ACCESSORY
*/
public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
@@ -270,6 +291,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is the audio device associated with a dock.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_DOCK
*/
public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
@@ -277,6 +299,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a USB audio headset.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_HEADSET
*/
public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
@@ -284,6 +307,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a hearing aid.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HEARING_AID
*/
public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
@@ -291,6 +315,7 @@ public final class MediaRoute2Info implements Parcelable {
* Indicates the route is a Bluetooth Low Energy (BLE) HEADSET.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLE_HEADSET
*/
public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
@@ -302,6 +327,7 @@ public final class MediaRoute2Info implements Parcelable {
* to provide a better experience on multichannel contents.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_MULTICHANNEL_GROUP
*/
@FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
@@ -421,6 +447,51 @@ public final class MediaRoute2Info implements Parcelable {
*/
public static final int TYPE_GROUP = 2000;
+ /** @hide */
+ @IntDef(
+ prefix = {"ROUTING_TYPE_"},
+ value = {
+ FLAG_ROUTING_TYPE_SYSTEM_AUDIO,
+ FLAG_ROUTING_TYPE_SYSTEM_VIDEO,
+ FLAG_ROUTING_TYPE_REMOTE
+ },
+ flag = true)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RoutingType {}
+
+ /**
+ * Indicates that a route supports routing of the system audio.
+ *
+ * <p>Providers that support this type of routing require the {@link
+ * android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission.
+ */
+ @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1;
+
+ /**
+ * Indicates that a route supports routing of the system video.
+ *
+ * @hide
+ */
+ // TODO: b/380431086 - Enable this API once we add support for system video routing.
+ @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int FLAG_ROUTING_TYPE_SYSTEM_VIDEO = 1 << 1;
+
+ /**
+ * Indicates that a route supports routing playback to remote routes through control commands.
+ *
+ * <p>This type of routing does not affect affect this system's audio or video, but instead
+ * relies on the device that corresponds to this route to fetch and play the media. It also
+ * requires the media app to take care of initializing and controlling playback.
+ */
+ @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int FLAG_ROUTING_TYPE_REMOTE = 1 << 2;
+
+ private static final int FLAG_ROUTING_TYPE_ALL =
+ FLAG_ROUTING_TYPE_SYSTEM_AUDIO
+ | FLAG_ROUTING_TYPE_SYSTEM_VIDEO
+ | FLAG_ROUTING_TYPE_REMOTE;
+
/**
* Route feature: Live audio.
* <p>
@@ -553,6 +624,7 @@ public final class MediaRoute2Info implements Parcelable {
private final List<String> mFeatures;
@Type
private final int mType;
+ @RoutingType private final int mRoutingTypeFlags;
private final boolean mIsSystem;
private final Uri mIconUri;
private final CharSequence mDescription;
@@ -569,6 +641,7 @@ public final class MediaRoute2Info implements Parcelable {
private final String mProviderId;
private final boolean mIsVisibilityRestricted;
private final Set<String> mAllowedPackages;
+ private final List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private final int mSuitabilityStatus;
MediaRoute2Info(@NonNull Builder builder) {
@@ -576,6 +649,7 @@ public final class MediaRoute2Info implements Parcelable {
mName = builder.mName;
mFeatures = builder.mFeatures;
mType = builder.mType;
+ mRoutingTypeFlags = builder.mRoutingTypeFlags;
mIsSystem = builder.mIsSystem;
mIconUri = builder.mIconUri;
mDescription = builder.mDescription;
@@ -592,6 +666,7 @@ public final class MediaRoute2Info implements Parcelable {
mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
mAllowedPackages = builder.mAllowedPackages;
mSuitabilityStatus = builder.mSuitabilityStatus;
+ mRequiredPermissions = List.copyOf(builder.mRequiredPermissions);
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -600,6 +675,7 @@ public final class MediaRoute2Info implements Parcelable {
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mFeatures = in.createStringArrayList();
mType = in.readInt();
+ mRoutingTypeFlags = validateRoutingTypeFlags(in.readInt());
mIsSystem = in.readBoolean();
mIconUri = in.readParcelable(null, android.net.Uri.class);
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -615,6 +691,12 @@ public final class MediaRoute2Info implements Parcelable {
mProviderId = in.readString();
mIsVisibilityRestricted = in.readBoolean();
mAllowedPackages = Set.of(in.createString8Array());
+ ArrayList<Set<String>> requiredPermissions = new ArrayList<>();
+ int numRequiredPermissionSets = in.readInt();
+ for (int i = 0; i < numRequiredPermissionSets; i++) {
+ requiredPermissions.add(Set.of(in.createString8Array()));
+ }
+ mRequiredPermissions = List.copyOf(requiredPermissions); // Use copyOf to make it immutable.
mSuitabilityStatus = in.readInt();
}
@@ -660,6 +742,13 @@ public final class MediaRoute2Info implements Parcelable {
return mType;
}
+ /** Returns the flags that indicate the routing types supported by this route. */
+ @RoutingType
+ @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public int getSupportedRoutingTypes() {
+ return mRoutingTypeFlags;
+ }
+
/**
* Returns whether the route is a system route or not.
* <p>
@@ -851,6 +940,16 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * @return a list of permission sets - all the permissions in at least one of these sets must be
+ * held to see this route.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+ public List<Set<String>> getRequiredPermissions() {
+ return mRequiredPermissions;
+ }
+
+ /**
* Returns whether this route's type can only be published by the system route provider.
*
* @see #isSystemRoute()
@@ -904,6 +1003,7 @@ public final class MediaRoute2Info implements Parcelable {
pw.println(indent + "mName=" + mName);
pw.println(indent + "mFeatures=" + mFeatures);
pw.println(indent + "mType=" + getDeviceTypeString(mType));
+ pw.println(indent + "mRoutingTypeFlags=" + getRoutingTypeFlagsString(mRoutingTypeFlags));
pw.println(indent + "mIsSystem=" + mIsSystem);
pw.println(indent + "mIconUri=" + mIconUri);
pw.println(indent + "mDescription=" + mDescription);
@@ -920,6 +1020,7 @@ public final class MediaRoute2Info implements Parcelable {
pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
+ pw.println(indent + "mRequiredPermissions=" + mRequiredPermissions);
}
private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -941,6 +1042,7 @@ public final class MediaRoute2Info implements Parcelable {
&& Objects.equals(mName, other.mName)
&& Objects.equals(mFeatures, other.mFeatures)
&& (mType == other.mType)
+ && (mRoutingTypeFlags == other.mRoutingTypeFlags)
&& (mIsSystem == other.mIsSystem)
&& Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mDescription, other.mDescription)
@@ -955,6 +1057,7 @@ public final class MediaRoute2Info implements Parcelable {
&& Objects.equals(mProviderId, other.mProviderId)
&& (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
&& Objects.equals(mAllowedPackages, other.mAllowedPackages)
+ && Objects.equals(mRequiredPermissions, other.mRequiredPermissions)
&& mSuitabilityStatus == other.mSuitabilityStatus;
}
@@ -966,6 +1069,7 @@ public final class MediaRoute2Info implements Parcelable {
mName,
mFeatures,
mType,
+ mRoutingTypeFlags,
mIsSystem,
mIconUri,
mDescription,
@@ -980,6 +1084,7 @@ public final class MediaRoute2Info implements Parcelable {
mProviderId,
mIsVisibilityRestricted,
mAllowedPackages,
+ mRequiredPermissions,
mSuitabilityStatus);
}
@@ -994,6 +1099,8 @@ public final class MediaRoute2Info implements Parcelable {
.append(getName())
.append(", type=")
.append(getDeviceTypeString(getType()))
+ .append(", routingTypes=")
+ .append(getRoutingTypeFlagsString(getSupportedRoutingTypes()))
.append(", isSystem=")
.append(isSystemRoute())
.append(", features=")
@@ -1018,6 +1125,9 @@ public final class MediaRoute2Info implements Parcelable {
.append(mIsVisibilityRestricted)
.append(", allowedPackages=")
.append(String.join(",", mAllowedPackages))
+ .append(", mRequiredPermissions=")
+ .append(mRequiredPermissions.stream().map(set -> String.join(",", set)).collect(
+ Collectors.joining("),(", "(", ")")))
.append(", suitabilityStatus=")
.append(mSuitabilityStatus)
.append(" }")
@@ -1035,6 +1145,7 @@ public final class MediaRoute2Info implements Parcelable {
TextUtils.writeToParcel(mName, dest, flags);
dest.writeStringList(mFeatures);
dest.writeInt(mType);
+ dest.writeInt(mRoutingTypeFlags);
dest.writeBoolean(mIsSystem);
dest.writeParcelable(mIconUri, flags);
TextUtils.writeToParcel(mDescription, dest, flags);
@@ -1050,6 +1161,10 @@ public final class MediaRoute2Info implements Parcelable {
dest.writeString(mProviderId);
dest.writeBoolean(mIsVisibilityRestricted);
dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+ dest.writeInt(mRequiredPermissions.size());
+ for (Set<String> permissionSet : mRequiredPermissions) {
+ dest.writeString8Array(permissionSet.toArray(new String[0]));
+ }
dest.writeInt(mSuitabilityStatus);
}
@@ -1143,6 +1258,34 @@ public final class MediaRoute2Info implements Parcelable {
}
}
+ /** Returns a human-readable representation of the given {@code routingTypeFlags}. */
+ private static String getRoutingTypeFlagsString(@RoutingType int routingTypeFlags) {
+ List<String> typeStrings = new ArrayList<>();
+ if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_AUDIO) != 0) {
+ typeStrings.add("SYSTEM_AUDIO");
+ }
+ if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_VIDEO) != 0) {
+ typeStrings.add("SYSTEM_VIDEO");
+ }
+ if ((routingTypeFlags & FLAG_ROUTING_TYPE_REMOTE) != 0) {
+ typeStrings.add("REMOTE");
+ }
+ return String.join(/* delimiter= */ "|", typeStrings);
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the provided {@code routingTypeFlags} are not
+ * valid. Otherwise, returns the provided value.
+ */
+ private static int validateRoutingTypeFlags(@RoutingType int routingTypeFlags) {
+ if (routingTypeFlags == 0 || (routingTypeFlags & ~FLAG_ROUTING_TYPE_ALL) != 0) {
+ throw new IllegalArgumentException(
+ "Invalid routing type flags: " + Integer.toHexString(routingTypeFlags));
+ } else {
+ return routingTypeFlags;
+ }
+ }
+
/**
* Builder for {@link MediaRoute2Info media route info}.
*/
@@ -1153,6 +1296,7 @@ public final class MediaRoute2Info implements Parcelable {
@Type
private int mType = TYPE_UNKNOWN;
+ @RoutingType private int mRoutingTypeFlags = FLAG_ROUTING_TYPE_REMOTE;
private boolean mIsSystem;
private Uri mIconUri;
private CharSequence mDescription;
@@ -1169,6 +1313,7 @@ public final class MediaRoute2Info implements Parcelable {
private String mProviderId;
private boolean mIsVisibilityRestricted;
private Set<String> mAllowedPackages;
+ private List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private int mSuitabilityStatus;
/**
@@ -1194,6 +1339,7 @@ public final class MediaRoute2Info implements Parcelable {
mDeduplicationIds = Set.of();
mAllowedPackages = Set.of();
mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+ mRequiredPermissions = List.of();
}
/**
@@ -1224,6 +1370,7 @@ public final class MediaRoute2Info implements Parcelable {
mName = routeInfo.mName;
mFeatures = new ArrayList<>(routeInfo.mFeatures);
mType = routeInfo.mType;
+ mRoutingTypeFlags = routeInfo.mRoutingTypeFlags;
mIsSystem = routeInfo.mIsSystem;
mIconUri = routeInfo.mIconUri;
mDescription = routeInfo.mDescription;
@@ -1242,6 +1389,7 @@ public final class MediaRoute2Info implements Parcelable {
mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
mAllowedPackages = routeInfo.mAllowedPackages;
mSuitabilityStatus = routeInfo.mSuitabilityStatus;
+ mRequiredPermissions = routeInfo.mRequiredPermissions;
}
/**
@@ -1301,6 +1449,18 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Sets the routing types that this route supports.
+ *
+ * @see MediaRoute2Info#getSupportedRoutingTypes()
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public Builder setSupportedRoutingTypes(@RoutingType int routingTypeFlags) {
+ mRoutingTypeFlags = validateRoutingTypeFlags(routingTypeFlags);
+ return this;
+ }
+
+ /**
* Sets whether the route is a system route or not.
* @hide
*/
@@ -1461,6 +1621,7 @@ public final class MediaRoute2Info implements Parcelable {
public Builder setVisibilityPublic() {
mIsVisibilityRestricted = false;
mAllowedPackages = Set.of();
+ mRequiredPermissions = List.of();
return this;
}
@@ -1485,6 +1646,37 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Limits the visibility of this route to holders of a set of permissions.
+ *
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
+ * @param requiredPermissions the list of all permissions which must be held in order to
+ * see this route.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+ public Builder setRequiredPermissions(@NonNull Set<String> requiredPermissions) {
+ return setRequiredPermissions(List.of(requiredPermissions));
+ }
+
+ /**
+ * Limits the visibility of this route to holders of one of a set of permissions.
+ *
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
+ * @param requiresOneOf a list of Sets of permissions. Holding all permissions in at least
+ * one of the Sets is required for the route to be visible.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+ public Builder setRequiredPermissions(@NonNull List<Set<String>> requiresOneOf) {
+ mRequiredPermissions = List.copyOf(requiresOneOf);
+ return this;
+ }
+
+ /**
* Sets route suitability status.
*
* <p>The default value is {@link
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index a14f1fd15f27..547099f044b2 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@ package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -81,6 +83,22 @@ public abstract class MediaRoute2ProviderService extends Service {
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
/**
+ * {@link Intent} action that indicates that the declaring service supports routing of the
+ * system media.
+ *
+ * <p>Providers must include this action if they intend to publish routes that support the
+ * system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * @see #onCreateSystemRoutingSession
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
+ "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
+
+ /**
* A category indicating that the associated provider is only intended for use within the app
* that hosts the provider.
*
@@ -138,12 +156,26 @@ public abstract class MediaRoute2ProviderService extends Service {
public static final int REASON_INVALID_COMMAND = 4;
/**
+ * The request has failed because the requested operation is not implemented by the provider.
+ *
+ * @see #notifyRequestFailed
* @hide
*/
- @IntDef(prefix = "REASON_", value = {
- REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
- REASON_INVALID_COMMAND
- })
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int REASON_UNIMPLEMENTED = 5;
+
+ /** @hide */
+ @IntDef(
+ prefix = "REASON_",
+ value = {
+ REASON_UNKNOWN_ERROR,
+ REASON_REJECTED,
+ REASON_NETWORK_ERROR,
+ REASON_ROUTE_NOT_AVAILABLE,
+ REASON_INVALID_COMMAND,
+ REASON_UNIMPLEMENTED
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -282,6 +314,32 @@ public abstract class MediaRoute2ProviderService extends Service {
}
/**
+ * Notifies the system of the successful creation of a system media routing session.
+ *
+ * <p>This method can only be called as the result of a prior call to {@link
+ * #onCreateSystemRoutingSession}.
+ *
+ * @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
+ * is in response to.
+ * @param sessionInfo a {@link RoutingSessionInfo} that describes the newly created routing
+ * session.
+ * @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
+ * MediaStreams} to return.
+ * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
+ * newly created routing session.
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @NonNull
+ public final MediaStreams notifySystemMediaSessionCreated(
+ long requestId,
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaStreamsFormats formats) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Notifies the existing session is updated. For example, when
* {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
*/
@@ -399,6 +457,43 @@ public abstract class MediaRoute2ProviderService extends Service {
@NonNull String routeId, @Nullable Bundle sessionHints);
/**
+ * Called when the service receives a request to create a system routing session.
+ *
+ * <p>This method will only be called for routes that support routing of the system media, as
+ * described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * <p>Implementors of this method must call {@link #notifySystemMediaSessionCreated} with the
+ * given {@code requestId} to indicate a successful session creation. If the session creation
+ * fails (for example, if the connection to the receiver device fails), the implementor must
+ * call {@link #notifyRequestFailed}, passing the {@code requestId}.
+ *
+ * <p>Unlike {@link #onCreateSession}, system sessions route the system media (for example,
+ * audio and/or video) which is to be retrieved by calling {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * <p>Changes to the session can be notified by calling {@link #notifySessionUpdated}.
+ *
+ * @param requestId the ID of this request
+ * @param packageName the package name of the application whose media to route.
+ * @param routeId the ID of the route initially being {@link
+ * RoutingSessionInfo#getSelectedRoutes() selected}.
+ * @param sessionHints an optional bundle of arguments sent by {@link MediaRouter2}, or null if
+ * none.
+ * @see RoutingSessionInfo.Builder
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public void onCreateSystemRoutingSession(
+ long requestId,
+ @NonNull String packageName,
+ @NonNull String routeId,
+ @Nullable Bundle sessionHints) {
+ mHandler.post(() -> notifyRequestFailed(requestId, REASON_UNIMPLEMENTED));
+ }
+
+ /**
* Called when the session should be released. A client of the session or system can request
* a session to be released.
* <p>
@@ -735,4 +830,100 @@ public abstract class MediaRoute2ProviderService extends Service {
MediaRoute2ProviderService.this, requestId, sessionId));
}
}
+
+ /**
+ * Holds the streams to be routed as part of a system media routing session.
+ *
+ * <p>The encoded data format matches the {@link MediaStreamsFormats} passed to {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreams {
+
+ private final AudioRecord mAudioRecord;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreams(AudioRecord mAudioRecord) {
+ this.mAudioRecord = mAudioRecord;
+ }
+
+ /**
+ * Returns the {@link AudioRecord} from which to read the audio data to route, or null if
+ * the routing session doesn't include audio.
+ */
+ @Nullable
+ public AudioRecord getAudioRecord() {
+ return mAudioRecord;
+ }
+ }
+
+ /**
+ * Holds the formats to encode media data to be read from {@link MediaStreams}.
+ *
+ * @see MediaStreams
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreamsFormats {
+
+ private final AudioFormat mAudioFormat;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreamsFormats(Builder builder) {
+ this.mAudioFormat = builder.mAudioFormat;
+ }
+
+ /**
+ * Returns the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Builder for {@link MediaStreamsFormats}
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class Builder {
+ private AudioFormat mAudioFormat;
+
+ /**
+ * Sets the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @param audioFormat the audio format
+ * @return this builder
+ */
+ @NonNull
+ public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
+ this.mAudioFormat = Objects.requireNonNull(audioFormat);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaStreamsFormats} instance.
+ *
+ * @return the built {@link MediaStreamsFormats} instance
+ */
+ @NonNull
+ public MediaStreamsFormats build() {
+ return new MediaStreamsFormats(this);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index b4dee0cab24e..fa1349c61c4c 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -16,4 +16,16 @@ flag {
name: "stop_media_projection_on_call_end"
description: "Stops MediaProjection sessions when a call ends"
bug: "368336349"
-} \ No newline at end of file
+}
+
+flag {
+ name: "media_projection_connected_display_no_virtual_device"
+ namespace: "media_projection"
+ description: "Filter out display associated with a virtual device for media projection use case"
+ bug: "362720120"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_exported: true
+}
+
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index f7f10df5786a..4f7132ad9ab2 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -324,6 +324,19 @@ public final class MediaProjection {
}
/**
+ * Stops projection.
+ * @hide
+ */
+ public void stop(@StopReason int stopReason) {
+ try {
+ Log.d(TAG, "Content Recording: stopping projection");
+ mImpl.stop(stopReason);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to stop projection", e);
+ }
+ }
+
+ /**
* Get the underlying IMediaProjection.
* @hide
*/
diff --git a/media/java/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/ActiveProcessingPicture.aidl
new file mode 100644
index 000000000000..2851306f6e4d
--- /dev/null
+++ b/media/java/android/media/quality/ActiveProcessingPicture.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable ActiveProcessingPicture; \ No newline at end of file
diff --git a/media/java/android/media/quality/ActiveProcessingPicture.java b/media/java/android/media/quality/ActiveProcessingPicture.java
new file mode 100644
index 000000000000..e16ad62e23f2
--- /dev/null
+++ b/media/java/android/media/quality/ActiveProcessingPicture.java
@@ -0,0 +1,86 @@
+/*
+ * 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.FlaggedApi;
+import android.media.tv.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Active picture represents an image or video undergoing picture processing which uses a picture
+ * profile. The picture profile is used to configure the picture processing parameters.
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class ActiveProcessingPicture implements Parcelable {
+ private final int mId;
+ private final String mProfileId;
+
+ public ActiveProcessingPicture(int id, @NonNull String profileId) {
+ mId = id;
+ mProfileId = profileId;
+ }
+
+ /** @hide */
+ ActiveProcessingPicture(Parcel in) {
+ mId = in.readInt();
+ mProfileId = in.readString();
+ }
+
+ @NonNull
+ public static final Creator<ActiveProcessingPicture> CREATOR = new Creator<>() {
+ @Override
+ public ActiveProcessingPicture createFromParcel(Parcel in) {
+ return new ActiveProcessingPicture(in);
+ }
+
+ @Override
+ public ActiveProcessingPicture[] newArray(int size) {
+ return new ActiveProcessingPicture[size];
+ }
+ };
+
+ /**
+ * An ID that uniquely identifies the active content.
+ *
+ * <p>The ID is assigned by the system to distinguish different active contents.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * The ID of the picture profile used to configure the content.
+ */
+ @NonNull
+ public String getProfileId() {
+ return mProfileId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeString(mProfileId);
+ }
+}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index ad19d0456ab0..c295946e50aa 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -110,9 +110,10 @@ public final class AmbientBacklightMetadata implements Parcelable {
/**
* Gets the number of horizontal color zones.
*
- * <p>A color zone is a group of lights that always display the same color.
+ * <p>A color zone is represented by one single aggregated color. The number should not be
+ * larger than 128.
*/
- @IntRange(from = 0)
+ @IntRange(from = 0, to = 128)
public int getHorizontalZonesNumber() {
return mHorizontalZonesNumber;
}
@@ -120,9 +121,10 @@ public final class AmbientBacklightMetadata implements Parcelable {
/**
* Gets the number of vertical color zones.
*
- * <p>A color zone is a group of lights that always display the same color.
+ * <p>A color zone is represented by one single aggregated color. The number should not be
+ * larger than 80.
*/
- @IntRange(from = 0)
+ @IntRange(from = 0, to = 80)
public int getVerticalZonesNumber() {
return mVerticalZonesNumber;
}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index b7e75b7e6649..253c2d896d63 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -23,49 +23,58 @@ import android.media.quality.ISoundProfileCallback;
import android.media.quality.ParamCapability;
import android.media.quality.PictureProfileHandle;
import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfileHandle;
import android.media.quality.SoundProfile;
+import android.os.UserHandle;
/**
* Interface for Media Quality Manager
* @hide
*/
interface IMediaQualityManager {
- PictureProfile createPictureProfile(in PictureProfile pp);
- void updatePictureProfile(in String id, in PictureProfile pp);
- void removePictureProfile(in String id);
- PictureProfile getPictureProfile(in int type, in String name);
- List<PictureProfile> getPictureProfilesByPackage(in String packageName);
- List<PictureProfile> getAvailablePictureProfiles();
- List<String> getPictureProfilePackageNames();
- List<String> getPictureProfileAllowList();
- void setPictureProfileAllowList(in List<String> packages);
- PictureProfileHandle getPictureProfileHandle(in String id);
+ 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);
+ PictureProfile getPictureProfile(
+ in int type, in String name, in boolean includeParams, in UserHandle user);
+ List<PictureProfile> getPictureProfilesByPackage(
+ in String packageName, in boolean includeParams, in UserHandle user);
+ List<PictureProfile> getAvailablePictureProfiles(in boolean includeParams, 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);
- SoundProfile createSoundProfile(in SoundProfile pp);
- void updateSoundProfile(in String id, in SoundProfile pp);
- void removeSoundProfile(in String id);
- SoundProfile getSoundProfile(in int type, in String name);
- List<SoundProfile> getSoundProfilesByPackage(in String packageName);
- List<SoundProfile> getAvailableSoundProfiles();
- List<String> getSoundProfilePackageNames();
- List<String> getSoundProfileAllowList();
- void setSoundProfileAllowList(in List<String> packages);
+ 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 getSoundProfile(
+ in int type, in String name, in boolean includeParams, in UserHandle user);
+ List<SoundProfile> getSoundProfilesByPackage(
+ in String packageName, in boolean includeParams, in UserHandle user);
+ List<SoundProfile> getAvailableSoundProfiles(in boolean includeParams, 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);
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
- List<ParamCapability> getParamCapabilities(in List<String> names);
+ List<ParamCapability> getParamCapabilities(in List<String> names, in UserHandle user);
- boolean isSupported();
- void setAutoPictureQualityEnabled(in boolean enabled);
- boolean isAutoPictureQualityEnabled();
- void setSuperResolutionEnabled(in boolean enabled);
- boolean isSuperResolutionEnabled();
- void setAutoSoundQualityEnabled(in boolean enabled);
- boolean isAutoSoundQualityEnabled();
+ 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);
- void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
- void setAmbientBacklightEnabled(in boolean enabled);
- boolean isAmbientBacklightEnabled();
+ void setAmbientBacklightSettings(in AmbientBacklightSettings settings, in UserHandle user);
+ void setAmbientBacklightEnabled(in boolean enabled, in UserHandle user);
+ boolean isAmbientBacklightEnabled(in UserHandle user);
}
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 34aa2b061caf..7071a1684fa2 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -29,5 +29,5 @@ oneway interface IPictureProfileCallback {
void onPictureProfileUpdated(in String id, in PictureProfile p);
void onPictureProfileRemoved(in String id, in PictureProfile p);
void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
- void onError(in int err);
+ void onError(in String id, in int err);
}
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl
index 9043757316bc..30bb106ef34c 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -29,5 +29,5 @@ oneway interface ISoundProfileCallback {
void onSoundProfileUpdated(in String id, in SoundProfile p);
void onSoundProfileRemoved(in String id, in SoundProfile p);
void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
- void onError(in int err);
+ void onError(in String id, in int err);
}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 3fac74b2a37d..6a52bcba547a 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -74,6 +74,204 @@ public class MediaQualityContract {
*/
public static final String PARAMETER_SATURATION = "saturation";
+ /**
+ * The hue.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_HUE = "hue";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_BACKLIGHT = "backlight";
+
+ /**
+ * Adjust brightness in advance color engine. Similar to a "brightness" control on a TV
+ * but acts at a lower level.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness";
+
+ /**
+ * Adjust saturation in advance color engine. Similar to a "saturation" control on a TV
+ * but acts at a lower level.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation";
+
+ /**
+ * Adjust hue in advance color engine. Similar to a "hue" control on a TV but acts at a
+ * lower level.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue";
+
+ /**
+ * Advance setting for red offset. Adjust the black level of red color channels, it
+ * controls the minimum intensity of each color, affecting the shadows and
+ * dark areas of the image.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset";
+
+ /**
+ * Advance setting for green offset. Adjust the black level of green color channels, it
+ * controls the minimum intensity of each color, affecting the shadows and dark
+ * areas of the image.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset";
+
+ /**
+ * Advance setting for blue offset. Adjust the black level of blue color channels, it
+ * controls the minimum intensity of each color, affecting the shadows and dark areas
+ * of the image.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset";
+
+ /**
+ * Advance setting for red gain. Adjust the gain or amplification of the red color channels.
+ * They control the overall intensity and white balance of red.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain";
+
+ /**
+ * Advance setting for green gain. Adjust the gain or amplification of the green color
+ * channels. They control the overall intensity and white balance of green.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain";
+
+ /**
+ * Advance setting for blue gain. Adjust the gain or amplification of the blue color
+ * channels.They control the overall intensity and white balance of blue.
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_AI_PQ = "ai_pq";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_AI_SUPER_RESOLUTION = "ai_super_resolution";
+
+ /** Noise reduction.
+ * (Off, Low, Medium, High)
+ * @see android.hardware.tv.mediaquality.QualityLevel
+ *
+ * <p>Type: STRING
+ */
+ public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction";
+
+ /**
+ * MPEG (moving picture experts group) noise reduction
+ * (Off, Low, Medium, High)
+ * @see android.hardware.tv.mediaquality.QualityLevel
+ *
+ * <p>Type: STRING
+ * */
+ public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction";
+
+ /**
+ * Refine the flesh colors in the pictures without affecting the other colors on the screen.
+ * (Off, Low, Medium, High)
+ * @see android.hardware.tv.mediaquality.QualityLevel
+ *
+ * <p>Type: STRING
+ */
+ public static final String PARAMETER_FLESH_TONE = "flesh_tone";
+
+ /**
+ * Contour noise reduction.
+ * (Off, Low, Medium, High)
+ * @see android.hardware.tv.mediaquality.QualityLevel
+ *
+ * <p>Type: STRING
+ */
+ public static final String PARAMETER_DECONTOUR = "decontour";
+
+ /**
+ * Dynamically change picture luma to enhance contrast.
+ * (Off, Low, Medium, High)
+ * @see android.hardware.tv.mediaquality.QualityLevel
+ *
+ * <p>Type: STRING
+ */
+ public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control";
+
+ /**
+ * Enable/disable film mode
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_FILM_MODE = "film_mode";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+ /**
+ * Enable/disable blue color auto stretch
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_BLUE_STRETCH = "blue_stretch";
+
+ /**
+ * Enable/disable the overall color tuning feature.
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_COLOR_TUNE = "color_tune";
+
+ /**
+ * Adjust color temperature type
+ *
+ * <p>Type: INTEGER
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature";
+
+ /**
+ * Enable/disable globe dimming.
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming";
+
+ /**
+ * Enable/disable auto adjust picture parameter based on the TV content.
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED =
+ "auto_picture_quality_enabled";
+
+ /**
+ * Enable/disable auto upscaling the picture quality. It analyzes the lower-resolution
+ * image and uses its knowledge to invent the missing pixel, make the image look sharper.
+ *
+ * <p>Type: BOOLEAN
+ */
+ public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED =
+ "auto_super_resolution_enabled";
+
private PictureQuality() {
}
}
@@ -105,6 +303,129 @@ public class MediaQualityContract {
*/
public static final String PARAMETER_TREBLE = "treble";
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_SOUND_MODE = "sound_mode";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_SURROUND_SOUND = "surround_sound";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_EQUALIZER_DETAIL = "equalizer_detail";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_SPEAKERS = "speakers";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_SPEAKERS_DELAY = "speakers_delay";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_EARC = "earc";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_AUTO_VOLUME_CONTROL = "auto_volume_control";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOWN_MIX_MODE = "down_mix_mode";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_DRC = "dts_drc";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOLBY_AUDIO_PROCESSING = "dolby_audio_processing";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE =
+ "dolby_audio_processing_sound_mode";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER =
+ "dolby_audio_processing_volume_leveler";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER =
+ "dolby_audio_processing_surround_virtualizer";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS =
+ "dolby_audio_processing_dolby_atmos";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DIALOGUE_ENHANCER = "dialogue_enhancer";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X = "dts_virtual_x";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_TBHDX = "dts_virtual_x_tbhdx";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_LIMITER = "dts_virtual_x_limiter";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X =
+ "dts_virtual_x_tru_surround_x";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD =
+ "dts_virtual_x_tru_volume_hd";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY =
+ "dts_virtual_x_dialog_clarity";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_DEFINITION = "dts_virtual_x_definition";
+
+ /**
+ * @hide
+ */
+ public static final String PARAMETER_DTS_VIRTUAL_X_HEIGHT = "dts_virtual_x_height";
+
+
private SoundQuality() {
}
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 50055971d66d..7e87462b64de 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -20,11 +20,13 @@ import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.media.tv.flags.Flags;
import android.os.RemoteException;
+import android.os.UserHandle;
import androidx.annotation.RequiresPermission;
@@ -47,6 +49,7 @@ public final class MediaQualityManager {
private final IMediaQualityManager mService;
private final Context mContext;
+ private final UserHandle mUserHandle;
private final Object mLock = new Object();
// @GuardedBy("mLock")
private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
@@ -54,6 +57,9 @@ public final class MediaQualityManager {
private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
// @GuardedBy("mLock")
private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+ // @GuardedBy("mLock")
+ private final List<ActiveProcessingPictureListenerRecord> mApListenerRecords =
+ new ArrayList<>();
/**
@@ -61,6 +67,7 @@ public final class MediaQualityManager {
*/
public MediaQualityManager(Context context, IMediaQualityManager service) {
mContext = context;
+ mUserHandle = context.getUser();
mService = service;
IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
@Override
@@ -100,11 +107,11 @@ public final class MediaQualityManager {
}
}
@Override
- public void onError(int err) {
+ public void onError(String profileId, int err) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
- record.postError(err);
+ record.postError(profileId, err);
}
}
}
@@ -147,11 +154,11 @@ public final class MediaQualityManager {
}
}
@Override
- public void onError(int err) {
+ public void onError(String profileId, int err) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
- record.postError(err);
+ record.postError(profileId, err);
}
}
}
@@ -208,18 +215,21 @@ public final class MediaQualityManager {
}
}
-
/**
* Gets picture profile by given profile type and name.
*
+ * @param type the type of the profile.
+ * @param name the name of the profile.
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
* @return the corresponding picture profile if available; {@code null} if the name doesn't
- * exist.
+ * exist.
*/
@Nullable
public PictureProfile getPictureProfile(
- @PictureProfile.ProfileType int type, @NonNull String name) {
+ @PictureProfile.ProfileType int type, @NonNull String name, boolean includeParams) {
try {
- return mService.getPictureProfile(type, name);
+ return mService.getPictureProfile(type, name, includeParams, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -229,14 +239,18 @@ public final class MediaQualityManager {
/**
* Gets profiles that available to the given package.
*
+ * @param packageName the package name of the profiles.
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
* @hide
*/
@SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
+ public List<PictureProfile> getPictureProfilesByPackage(
+ @NonNull String packageName, boolean includeParams) {
try {
- return mService.getPictureProfilesByPackage(packageName);
+ return mService.getPictureProfilesByPackage(packageName, includeParams, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -244,11 +258,34 @@ public final class MediaQualityManager {
/**
* Gets profiles that available to the caller.
+ *
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
+ * @return the corresponding picture profile if available; {@code null} if the name doesn't
+ * exist.
*/
@NonNull
- public List<PictureProfile> getAvailablePictureProfiles() {
+ public List<PictureProfile> getAvailablePictureProfiles(boolean includeParams) {
+ try {
+ return mService.getAvailablePictureProfiles(includeParams, mUserHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets preferred default picture profile.
+ *
+ * @param id the ID of the default profile. {@code null} to unset the default profile.
+ * @return {@code true} if it's set successfully; {@code false} otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public boolean setDefaultPictureProfile(@Nullable String id) {
try {
- return mService.getAvailablePictureProfiles();
+ return mService.setDefaultPictureProfile(id, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -257,7 +294,7 @@ public final class MediaQualityManager {
/**
* Gets all package names whose picture profiles are available.
*
- * @see #getPictureProfilesByPackage(String)
+ * @see #getPictureProfilesByPackage(String, boolean)
* @hide
*/
@SystemApi
@@ -265,7 +302,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public List<String> getPictureProfilePackageNames() {
try {
- return mService.getPictureProfilePackageNames();
+ return mService.getPictureProfilePackageNames(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -275,9 +312,21 @@ public final class MediaQualityManager {
* Gets picture profile handle by profile ID.
* @hide
*/
- public PictureProfileHandle getPictureProfileHandle(String id) {
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id) {
try {
- return mService.getPictureProfileHandle(id);
+ return mService.getPictureProfileHandle(id, mUserHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets sound profile handle by profile ID.
+ * @hide
+ */
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id) {
+ try {
+ return mService.getSoundProfileHandle(id, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -288,10 +337,12 @@ public final class MediaQualityManager {
*
* <p>If the profile is created successfully,
* {@link PictureProfileCallback#onPictureProfileAdded(String, PictureProfile)} is invoked.
+ *
+ * @param pp the {@link PictureProfile} object to be created.
*/
public void createPictureProfile(@NonNull PictureProfile pp) {
try {
- mService.createPictureProfile(pp);
+ mService.createPictureProfile(pp, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -300,10 +351,13 @@ public final class MediaQualityManager {
/**
* Updates an existing picture profile and store it in the system.
+ *
+ * @param profileId the id of the object to be updated.
+ * @param pp the {@link PictureProfile} object to be updated.
*/
public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
try {
- mService.updatePictureProfile(profileId, pp);
+ mService.updatePictureProfile(profileId, pp, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -312,10 +366,12 @@ public final class MediaQualityManager {
/**
* Removes a picture profile from the system.
+ *
+ * @param profileId the id of the object to be removed.
*/
public void removePictureProfile(@NonNull String profileId) {
try {
- mService.removePictureProfile(profileId);
+ mService.removePictureProfile(profileId, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -351,18 +407,20 @@ public final class MediaQualityManager {
}
}
-
/**
* Gets sound profile by given profile type and name.
*
- * @return the corresponding sound profile if available; {@code null} if the name doesn't
- * exist.
+ * @param type the type of the profile.
+ * @param name the name of the profile.
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
+ * @return the corresponding sound profile if available; {@code null} if the name doesn't exist.
*/
@Nullable
public SoundProfile getSoundProfile(
- @SoundProfile.ProfileType int type, @NonNull String name) {
+ @SoundProfile.ProfileType int type, @NonNull String name, boolean includeParams) {
try {
- return mService.getSoundProfile(type, name);
+ return mService.getSoundProfile(type, name, includeParams, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -372,14 +430,18 @@ public final class MediaQualityManager {
/**
* Gets profiles that available to the given package.
*
+ * @param packageName the package name of the profiles.
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
* @hide
*/
@SystemApi
@NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
- public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) {
+ public List<SoundProfile> getSoundProfilesByPackage(
+ @NonNull String packageName, boolean includeParams) {
try {
- return mService.getSoundProfilesByPackage(packageName);
+ return mService.getSoundProfilesByPackage(packageName, includeParams, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -387,11 +449,34 @@ public final class MediaQualityManager {
/**
* Gets profiles that available to the caller package.
+ *
+ * @param includeParams {@code true} to include parameters in the profile; {@code false}
+ * otherwise.
+ *
+ * @return the corresponding sound profile if available; {@code null} if the none available.
*/
@NonNull
- public List<SoundProfile> getAvailableSoundProfiles() {
+ public List<SoundProfile> getAvailableSoundProfiles(boolean includeParams) {
try {
- return mService.getAvailableSoundProfiles();
+ return mService.getAvailableSoundProfiles(includeParams, mUserHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets preferred default sound profile.
+ *
+ * @param id the ID of the default profile. {@code null} to unset the default profile.
+ * @return {@code true} if it's set successfully; {@code false} otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public boolean setDefaultSoundProfile(@Nullable String id) {
+ try {
+ return mService.setDefaultSoundProfile(id, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -400,7 +485,7 @@ public final class MediaQualityManager {
/**
* Gets all package names whose sound profiles are available.
*
- * @see #getSoundProfilesByPackage(String)
+ * @see #getSoundProfilesByPackage(String, boolean)
*
* @hide
*/
@@ -409,7 +494,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public List<String> getSoundProfilePackageNames() {
try {
- return mService.getSoundProfilePackageNames();
+ return mService.getSoundProfilePackageNames(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -421,10 +506,12 @@ public final class MediaQualityManager {
*
* <p>If the profile is created successfully,
* {@link SoundProfileCallback#onSoundProfileAdded(String, SoundProfile)} is invoked.
+ *
+ * @param sp the {@link SoundProfile} object to be created.
*/
public void createSoundProfile(@NonNull SoundProfile sp) {
try {
- mService.createSoundProfile(sp);
+ mService.createSoundProfile(sp, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -433,10 +520,13 @@ public final class MediaQualityManager {
/**
* Updates an existing sound profile and store it in the system.
+ *
+ * @param profileId the id of the object to be updated.
+ * @param sp the {@link SoundProfile} object to be updated.
*/
public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
try {
- mService.updateSoundProfile(profileId, sp);
+ mService.updateSoundProfile(profileId, sp, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -445,10 +535,12 @@ public final class MediaQualityManager {
/**
* Removes a sound profile from the system.
+ *
+ * @param profileId the id of the object to be removed.
*/
public void removeSoundProfile(@NonNull String profileId) {
try {
- mService.removeSoundProfile(profileId);
+ mService.removeSoundProfile(profileId, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -460,7 +552,7 @@ public final class MediaQualityManager {
@NonNull
public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
try {
- return mService.getParamCapabilities(names);
+ return mService.getParamCapabilities(names, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -478,7 +570,7 @@ public final class MediaQualityManager {
@NonNull
public List<String> getPictureProfileAllowList() {
try {
- return mService.getPictureProfileAllowList();
+ return mService.getPictureProfileAllowList(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -492,7 +584,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);
+ mService.setPictureProfileAllowList(packageNames, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -510,7 +602,7 @@ public final class MediaQualityManager {
@NonNull
public List<String> getSoundProfileAllowList() {
try {
- return mService.getSoundProfileAllowList();
+ return mService.getSoundProfileAllowList(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -524,7 +616,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);
+ mService.setSoundProfileAllowList(packageNames, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -536,7 +628,7 @@ public final class MediaQualityManager {
*/
public boolean isSupported() {
try {
- return mService.isSupported();
+ return mService.isSupported(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -554,7 +646,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public void setAutoPictureQualityEnabled(boolean enabled) {
try {
- mService.setAutoPictureQualityEnabled(enabled);
+ mService.setAutoPictureQualityEnabled(enabled, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -565,7 +657,7 @@ public final class MediaQualityManager {
*/
public boolean isAutoPictureQualityEnabled() {
try {
- return mService.isAutoPictureQualityEnabled();
+ return mService.isAutoPictureQualityEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -582,7 +674,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
public void setSuperResolutionEnabled(boolean enabled) {
try {
- mService.setSuperResolutionEnabled(enabled);
+ mService.setSuperResolutionEnabled(enabled, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -593,7 +685,7 @@ public final class MediaQualityManager {
*/
public boolean isSuperResolutionEnabled() {
try {
- return mService.isSuperResolutionEnabled();
+ return mService.isSuperResolutionEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -611,7 +703,7 @@ public final class MediaQualityManager {
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public void setAutoSoundQualityEnabled(boolean enabled) {
try {
- mService.setAutoSoundQualityEnabled(enabled);
+ mService.setAutoSoundQualityEnabled(enabled, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -622,7 +714,7 @@ public final class MediaQualityManager {
*/
public boolean isAutoSoundQualityEnabled() {
try {
- return mService.isAutoSoundQualityEnabled();
+ return mService.isAutoSoundQualityEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -631,6 +723,7 @@ public final class MediaQualityManager {
/**
* Registers a {@link AmbientBacklightCallback}.
*/
+ @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
public void registerAmbientBacklightCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull AmbientBacklightCallback callback) {
@@ -644,6 +737,7 @@ public final class MediaQualityManager {
/**
* Unregisters the existing {@link AmbientBacklightCallback}.
*/
+ @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
public void unregisterAmbientBacklightCallback(
@NonNull final AmbientBacklightCallback callback) {
Preconditions.checkNotNull(callback);
@@ -664,11 +758,12 @@ public final class MediaQualityManager {
*
* @param settings The settings to use for the backlight detector.
*/
+ @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
public void setAmbientBacklightSettings(
@NonNull AmbientBacklightSettings settings) {
Preconditions.checkNotNull(settings);
try {
- mService.setAmbientBacklightSettings(settings);
+ mService.setAmbientBacklightSettings(settings, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -679,7 +774,7 @@ public final class MediaQualityManager {
*/
public boolean isAmbientBacklightEnabled() {
try {
- return mService.isAmbientBacklightEnabled();
+ return mService.isAmbientBacklightEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -690,9 +785,10 @@ public final class MediaQualityManager {
*
* @param enabled {@code true} to enable, {@code false} to disable.
*/
+ @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
public void setAmbientBacklightEnabled(boolean enabled) {
try {
- mService.setAmbientBacklightEnabled(enabled);
+ mService.setAmbientBacklightEnabled(enabled, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -749,11 +845,11 @@ public final class MediaQualityManager {
});
}
- public void postError(int error) {
+ public void postError(String profileId, int error) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
- mCallback.onError(error);
+ mCallback.onError(profileId, error);
}
});
}
@@ -809,11 +905,11 @@ public final class MediaQualityManager {
});
}
- public void postError(int error) {
+ public void postError(String profileId, int error) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
- mCallback.onError(error);
+ mCallback.onError(profileId, error);
}
});
}
@@ -879,9 +975,11 @@ public final class MediaQualityManager {
/**
* This is invoked when an issue has occurred.
*
+ * @param profileId the profile ID related to the error. {@code null} if there is no
+ * associated profile.
* @param errorCode the error code
*/
- public void onError(@PictureProfile.ErrorCode int errorCode) {
+ public void onError(@Nullable String profileId, @PictureProfile.ErrorCode int errorCode) {
}
/**
@@ -934,9 +1032,11 @@ public final class MediaQualityManager {
/**
* This is invoked when an issue has occurred.
*
+ * @param profileId the profile ID related to the error. {@code null} if there is no
+ * associated profile.
* @param errorCode the error code
*/
- public void onError(@SoundProfile.ErrorCode int errorCode) {
+ public void onError(@Nullable String profileId, @SoundProfile.ErrorCode int errorCode) {
}
/**
@@ -962,4 +1062,86 @@ public final class MediaQualityManager {
public void onAmbientBacklightEvent(@NonNull AmbientBacklightEvent event) {
}
}
+
+ /**
+ * Listener used to monitor status of active pictures.
+ */
+ public interface ActiveProcessingPictureListener {
+ /**
+ * Called when active pictures are changed.
+ *
+ * @param activeProcessingPictures contents currently undergoing picture processing.
+ */
+ void onActiveProcessingPicturesChanged(
+ @NonNull List<ActiveProcessingPicture> activeProcessingPictures);
+ }
+
+ /**
+ * Adds an active picture listener for the contents owner by the caller.
+ */
+ public void addActiveProcessingPictureListener(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull ActiveProcessingPictureListener listener) {
+ Preconditions.checkNotNull(listener);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mApListenerRecords.add(
+ new ActiveProcessingPictureListenerRecord(listener, executor, false));
+ }
+ }
+
+ /**
+ * Adds an active picture listener for all contents.
+ *
+ * @hide
+ */
+ @SuppressLint("PairedRegistration")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public void addGlobalActiveProcessingPictureListener(
+ @NonNull Executor executor,
+ @NonNull ActiveProcessingPictureListener listener) {
+ Preconditions.checkNotNull(listener);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mApListenerRecords.add(
+ new ActiveProcessingPictureListenerRecord(listener, executor, true));
+ }
+ }
+
+
+ /**
+ * Removes an active picture listener for the contents.
+ */
+ public void removeActiveProcessingPictureListener(
+ @NonNull ActiveProcessingPictureListener listener) {
+ Preconditions.checkNotNull(listener);
+ synchronized (mLock) {
+ for (Iterator<ActiveProcessingPictureListenerRecord> it = mApListenerRecords.iterator();
+ it.hasNext(); ) {
+ ActiveProcessingPictureListenerRecord record = it.next();
+ if (record.getListener() == listener) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ private static final class ActiveProcessingPictureListenerRecord {
+ private final ActiveProcessingPictureListener mListener;
+ private final Executor mExecutor;
+ private final boolean mIsGlobal;
+
+ ActiveProcessingPictureListenerRecord(
+ ActiveProcessingPictureListener listener, Executor executor, boolean isGlobal) {
+ mListener = listener;
+ mExecutor = executor;
+ mIsGlobal = isGlobal;
+ }
+
+ public ActiveProcessingPictureListener getListener() {
+ return mListener;
+ }
+ }
}
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index dcb4222c3eaf..6064485c1c38 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -48,6 +48,7 @@ public final class PictureProfile implements Parcelable {
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final PictureProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -121,6 +122,7 @@ public final class PictureProfile implements Parcelable {
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(PictureProfileHandle.class.getClassLoader());
}
@Override
@@ -131,6 +133,7 @@ public final class PictureProfile implements Parcelable {
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -163,13 +166,15 @@ public final class PictureProfile implements Parcelable {
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull PictureProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -251,6 +256,15 @@ public final class PictureProfile implements Parcelable {
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public PictureProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link PictureProfile}.
*/
public static final class Builder {
@@ -265,6 +279,7 @@ public final class PictureProfile implements Parcelable {
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private PictureProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -283,6 +298,7 @@ public final class PictureProfile implements Parcelable {
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -350,6 +366,16 @@ public final class PictureProfile implements Parcelable {
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull PictureProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -361,7 +387,8 @@ public final class PictureProfile implements Parcelable {
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java
index 714fd36d664a..d9d21932d09a 100644
--- a/media/java/android/media/quality/PictureProfileHandle.java
+++ b/media/java/android/media/quality/PictureProfileHandle.java
@@ -28,11 +28,14 @@ import android.os.Parcelable;
* A picture profile represents a collection of parameters used to configure picture processing
* to enhance the quality of graphic buffers.
*
+ * @see PictureProfile.getHandle
+ *
* @hide
*/
@SystemApi
@FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public final class PictureProfileHandle implements Parcelable {
+ /** A handle that represents no picture processing configuration. */
public static final @NonNull PictureProfileHandle NONE = new PictureProfileHandle(0);
private final long mId;
@@ -42,7 +45,16 @@ public final class PictureProfileHandle implements Parcelable {
mId = id;
}
- /** @hide */
+ /**
+ * An ID that uniquely identifies the picture profile across the system.
+ *
+ * This ID can be used to construct an NDK PictureProfileHandle to be fed directly into
+ * IGraphicBufferProducer to couple a picture profile to a graphic buffer.
+ *
+ * Note: These IDs are generated randomly and are not stable across reboots.
+ *
+ * @hide
+ */
@SystemApi
@FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
public long getId() {
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index c7fb4dd8486f..1dd59ab0903b 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -48,6 +48,7 @@ public final class SoundProfile implements Parcelable {
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final SoundProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -120,6 +121,7 @@ public final class SoundProfile implements Parcelable {
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(SoundProfileHandle.class.getClassLoader());
}
@Override
@@ -130,6 +132,7 @@ public final class SoundProfile implements Parcelable {
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -162,13 +165,15 @@ public final class SoundProfile implements Parcelable {
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull SoundProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -250,6 +255,15 @@ public final class SoundProfile implements Parcelable {
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public SoundProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link SoundProfile}
*/
public static final class Builder {
@@ -264,6 +278,7 @@ public final class SoundProfile implements Parcelable {
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private SoundProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -282,6 +297,7 @@ public final class SoundProfile implements Parcelable {
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -349,6 +365,16 @@ public final class SoundProfile implements Parcelable {
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull SoundProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -360,7 +386,8 @@ public final class SoundProfile implements Parcelable {
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/media/java/android/media/quality/SoundProfileHandle.aidl b/media/java/android/media/quality/SoundProfileHandle.aidl
new file mode 100644
index 000000000000..6b8161c8cc43
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfileHandle.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable SoundProfileHandle;
diff --git a/media/java/android/media/quality/SoundProfileHandle.java b/media/java/android/media/quality/SoundProfileHandle.java
new file mode 100644
index 000000000000..edb546efdaf3
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfileHandle.java
@@ -0,0 +1,72 @@
+/*
+ * 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/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 65e83b9bf204..8fe543656ed9 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -328,7 +328,7 @@ public final class SoundTriggerDetector {
mRecognitionCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setAudioCapabilities(audioCapabilities)
.build(),
runInBatterySaver);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 4b832aee49c5..3451dfc559ee 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -93,7 +93,7 @@ flag {
name: "set_resource_holder_retain"
is_exported: true
namespace: "media_tv"
- description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+ description: "Feature flag to add setResourceOwnershipRetention api to MediaCas and Tuner JAVA."
bug: "372973197"
}
@@ -112,3 +112,11 @@ flag {
description : "Feature flag to enable APIs for applying picture profiles"
bug: "337330263"
}
+
+flag {
+ name: "hdmi_control_collect_physical_address"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Collect physical address from HDMI-CEC messages in metrics"
+ bug: "376001043"
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b1adb77f9543..7f7a23969411 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -757,14 +757,14 @@ public class Tuner implements AutoCloseable {
* scenario, when both resource holder and resource challenger have same processId and same
* priority.
*
- * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
- * false to allow the resource challenger to acquire the resource. If not explicitly set,
- * resourceHolderRetain is set to false.
+ * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+ * or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
*/
@FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
- public void setResourceHolderRetain(boolean resourceHolderRetain) {
- mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ public void setResourceOwnershipRetention(boolean enabled) {
+ mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
}
/**
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index be65ad98c35b..2ed642eecb81 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -227,15 +228,16 @@ public class TunerResourceManager {
* scenario, when both resource holder and resource challenger have same processId and same
* priority.
*
- * @param clientId The client id used to set ownership of resource to owner in case of resource
+ * @param clientId The client id used to set ownership of resource in case of resource
* challenger situation.
- * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
- * false to allow the resource challenger to acquire the resource. If not explicitly set,
- * resourceHolderRetain is set to false.
+ * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+ * or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
*/
- public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ public void setResourceOwnershipRetention(int clientId, boolean enabled) {
try {
- mService.setResourceHolderRetain(clientId, resourceHolderRetain);
+ mService.setResourceOwnershipRetention(clientId, enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index c57be1b09b66..50f9fe556cee 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -156,12 +156,13 @@ interface ITunerResourceManager {
* scenario, when both Resource Holder and Resource Challenger have same processId and same
* priority.
*
- * @param clientId The resourceHolderRetain of the client is updated using client ID.
- * @param resourceHolderRetain set to true to allow the Resource Holder to retain ownership, or
- * false to allow the Resource Challenger to acquire the resource. If not explicitly set,
- * resourceHolderRetain is set to false.
+ * @param clientId The client id used to set ownership of resource in case of resource
+ * challenger situation.
+ * @param enabled Set to {@code true} to allow the Resource Holder to retain ownership,
+ * or false to allow the Resource Challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
*/
- void setResourceHolderRetain(int clientId, boolean resourceHolderRetain);
+ void setResourceOwnershipRetention(int clientId, boolean enabled);
/*
* This API is used by the Tuner framework to request a frontend from the TunerHAL.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 88c1c434cc0d..ec336d5efee6 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -27,6 +27,7 @@ import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.CameraMetadataInfo;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -217,7 +218,7 @@ public class CameraBinderTest extends AndroidTestCase {
* android.hardware.camera2.CaptureResultExtras)
*/
@Override
- public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+ public void onResultReceived(CameraMetadataInfo result, CaptureResultExtras resultExtras,
PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 3758c515c1a5..7d1e5f821c05 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -33,6 +33,7 @@ import android.graphics.SurfaceTexture;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadataInfo;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
@@ -141,7 +142,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
* android.hardware.camera2.impl.CameraMetadataNative,
* android.hardware.camera2.CaptureResultExtras)
*/
- public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+ public void onResultReceived(CameraMetadataInfo result, CaptureResultExtras resultExtras,
PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
// TODO Auto-generated method stub
@@ -186,10 +187,14 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
}
}
- class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataNative> {
+ class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataInfo> {
@Override
- public boolean matches(CameraMetadataNative obj) {
- return !obj.isEmpty();
+ public boolean matches(CameraMetadataInfo obj) {
+ if (obj.getTag() == CameraMetadataInfo.metadata) {
+ return !(obj.getMetadata().isEmpty());
+ } else {
+ return (obj.getFmqSize() != 0);
+ }
}
}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index cd6de5a5c8f0..129d6163010e 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -73,6 +73,7 @@ cc_library_shared {
"surface_control.cpp",
"surface_texture.cpp",
"system_fonts.cpp",
+ "system_health.cpp",
"trace.cpp",
"thermal.cpp",
],
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 9a3527da9623..1fde7d268517 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -2,7 +2,7 @@ jreck@google.com #{LAST_RESORT_SUGGESTION}
# General NDK API reviewers
per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com
-per-file libandroid.map.txt = jreck@google.com, zyy@google.com
+per-file libandroid.map.txt = jreck@google.com, zyy@google.com, mattbuckley@google.com
# Networking
per-file libandroid_net.map.txt, net.c = set noparent
@@ -31,3 +31,4 @@ per-file input.cpp = file:/INPUT_OWNERS
# PerformanceHint
per-file performance_hint.cpp = file:/ADPF_OWNERS
+per-file thermal.cpp = file:/ADPF_OWNERS
diff --git a/native/android/display_luts.cpp b/native/android/display_luts.cpp
index 179a32bd1c03..b03a718d4a65 100644
--- a/native/android/display_luts.cpp
+++ b/native/android/display_luts.cpp
@@ -26,8 +26,9 @@
#define CHECK_NOT_NULL(name) \
LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
-ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length, int32_t dimension,
- int32_t key) {
+ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length,
+ ADisplayLuts_Dimension dimension,
+ ADisplayLuts_SamplingKey key) {
CHECK_NOT_NULL(buffer);
LOG_ALWAYS_FATAL_IF(length >= ADISPLAYLUTS_BUFFER_LENGTH_LIMIT,
"the lut raw buffer length is too big to handle");
@@ -64,7 +65,7 @@ void ADisplayLutsEntry_destroy(ADisplayLutsEntry* entry) {
ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* entry) {
CHECK_NOT_NULL(entry);
- return static_cast<ADisplayLuts_Dimension>(entry->properties.dimension);
+ return entry->properties.dimension;
}
int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) {
@@ -74,7 +75,7 @@ int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) {
ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* entry) {
CHECK_NOT_NULL(entry);
- return static_cast<ADisplayLuts_SamplingKey>(entry->properties.samplingKey);
+ return entry->properties.samplingKey;
}
const float* ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry) {
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
index 532213611cf1..074973188c66 100644
--- a/native/android/dynamic_instrumentation_manager.cpp
+++ b/native/android/dynamic_instrumentation_manager.cpp
@@ -15,7 +15,9 @@
*/
#define LOG_TAG "ADynamicInstrumentationManager"
+#include <android-base/properties.h>
#include <android/dynamic_instrumentation_manager.h>
+#include <android/os/instrumentation/BnOffsetCallback.h>
#include <android/os/instrumentation/ExecutableMethodFileOffsets.h>
#include <android/os/instrumentation/IDynamicInstrumentationManager.h>
#include <android/os/instrumentation/MethodDescriptor.h>
@@ -23,7 +25,9 @@
#include <binder/Binder.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+#include <future>
#include <mutex>
#include <optional>
#include <string>
@@ -31,6 +35,9 @@
namespace android::dynamicinstrumentationmanager {
+using android::os::instrumentation::BnOffsetCallback;
+using android::os::instrumentation::ExecutableMethodFileOffsets;
+
// Global instance of IDynamicInstrumentationManager, service is obtained only on first use.
static std::mutex mLock;
static sp<os::instrumentation::IDynamicInstrumentationManager> mService;
@@ -131,6 +138,30 @@ void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
delete instance;
}
+class ResultCallback : public BnOffsetCallback {
+public:
+ ::android::binder::Status onResult(
+ const ::std::optional<ExecutableMethodFileOffsets>& offsets) override {
+ promise_.set_value(offsets);
+ return android::binder::Status::ok();
+ }
+
+ std::optional<ExecutableMethodFileOffsets> waitForResult() {
+ std::future<std::optional<ExecutableMethodFileOffsets>> futureResult =
+ promise_.get_future();
+ auto futureStatus = futureResult.wait_for(
+ std::chrono::seconds(1 * android::base::HwTimeoutMultiplier()));
+ if (futureStatus == std::future_status::ready) {
+ return futureResult.get();
+ } else {
+ return std::nullopt;
+ }
+ }
+
+private:
+ std::promise<std::optional<ExecutableMethodFileOffsets>> promise_;
+};
+
int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
const ADynamicInstrumentationManager_TargetProcess* targetProcess,
const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor,
@@ -150,15 +181,15 @@ int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
return INVALID_OPERATION;
}
- std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets;
+ android::sp<ResultCallback> resultCallback = android::sp<ResultCallback>::make();
binder_status_t result =
service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel,
- &offsets)
+ resultCallback)
.exceptionCode();
if (result != OK) {
return result;
}
-
+ std::optional<ExecutableMethodFileOffsets> offsets = resultCallback->waitForResult();
if (offsets != std::nullopt) {
auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
value->containerPath = offsets->containerPath;
@@ -170,4 +201,4 @@ int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
}
return result;
-} \ No newline at end of file
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 7f555a868615..1ccadf90c2a9 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -301,7 +301,6 @@ LIBANDROID {
ASurfaceTransaction_setEnableBackPressure; # introduced=31
ASurfaceTransaction_setFrameRate; # introduced=30
ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
- ASurfaceTransaction_setFrameRateParams; # introduced=36
ASurfaceTransaction_clearFrameRate; # introduced=34
ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
ASurfaceTransaction_setGeometry; # introduced=29
@@ -321,6 +320,23 @@ LIBANDROID {
ASystemFontIterator_open; # introduced=29
ASystemFontIterator_close; # introduced=29
ASystemFontIterator_next; # introduced=29
+ ASystemHealth_getCpuHeadroom; # introduced=36
+ ASystemHealth_getGpuHeadroom; # introduced=36
+ ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36
+ ASystemHealth_getGpuHeadroomMinIntervalMillis; # introduced=36
+ ACpuHeadroomParams_create; # introduced=36
+ ACpuHeadroomParams_destroy; # introduced=36
+ ACpuHeadroomParams_setCalculationType; # introduced=36
+ ACpuHeadroomParams_getCalculationType; # introduced=36
+ ACpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_getCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_setTids; # introduced=36
+ AGpuHeadroomParams_create; # introduced=36
+ AGpuHeadroomParams_destroy; # introduced=36
+ AGpuHeadroomParams_setCalculationType; # introduced=36
+ AGpuHeadroomParams_getCalculationType; # introduced=36
+ AGpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ AGpuHeadroomParams_getCalculationWindowMillis; # introduced=36
AFont_close; # introduced=29
AFont_getFontFilePath; # introduced=29
AFont_getWeight; # introduced=29
@@ -362,6 +378,8 @@ LIBANDROID {
AThermal_unregisterThermalStatusListener; # introduced=30
AThermal_getThermalHeadroom; # introduced=31
AThermal_getThermalHeadroomThresholds; # introduced=VanillaIceCream
+ AThermal_registerThermalHeadroomListener; # introduced=36
+ AThermal_unregisterThermalHeadroomListener; # introduced=36
APerformanceHint_getManager; # introduced=Tiramisu
APerformanceHint_createSession; # introduced=Tiramisu
APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
@@ -375,7 +393,9 @@ LIBANDROID {
APerformanceHint_createSessionUsingConfig; # introduced=36
APerformanceHint_notifyWorkloadIncrease; # introduced=36
APerformanceHint_notifyWorkloadReset; # introduced=36
+ APerformanceHint_notifyWorkloadSpike; # introduced=36
APerformanceHint_borrowSessionFromJava; # introduced=36
+ APerformanceHint_setNativeSurfaces; # introduced=36
AWorkDuration_create; # introduced=VanillaIceCream
AWorkDuration_release; # introduced=VanillaIceCream
AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -388,6 +408,8 @@ LIBANDROID {
ASessionCreationConfig_setTargetWorkDurationNanos; # introduced=36
ASessionCreationConfig_setPreferPowerEfficiency; # introduced=36
ASessionCreationConfig_setGraphicsPipeline; # introduced=36
+ ASessionCreationConfig_setNativeSurfaces; # introduced=36
+ ASessionCreationConfig_setUseAutoTiming; # introduced=36
local:
*;
};
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 883e139cca0a..1945d90568b3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -29,13 +29,19 @@
#include <aidl/android/os/SessionCreationConfig.h>
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
+#include <android/binder_libbinder.h>
#include <android/binder_manager.h>
#include <android/binder_status.h>
+#include <android/native_window.h>
#include <android/performance_hint.h>
+#include <android/surface_control.h>
#include <android/trace.h>
#include <android_os.h>
#include <cutils/trace.h>
#include <fmq/AidlMessageQueue.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
#include <inttypes.h>
#include <jni_wrappers.h>
#include <performance_hint_private.h>
@@ -66,7 +72,12 @@ struct APerformanceHintSession;
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
struct AWorkDuration : public hal::WorkDuration {};
-struct ASessionCreationConfig : public SessionCreationConfig {};
+struct ASessionCreationConfig : public SessionCreationConfig {
+ std::vector<wp<IBinder>> layers{};
+ bool hasMode(hal::SessionMode&& mode) {
+ return std::find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end();
+ }
+};
bool kForceGraphicsPipeline = false;
@@ -158,6 +169,11 @@ public:
FMQWrapper& getFMQWrapper();
bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
void initJava(JNIEnv* _Nonnull env);
+ ndk::ScopedAIBinder_Weak x;
+ template <class T>
+ static void layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls, int numSurfaceControls,
+ std::vector<T>& out);
private:
// Necessary to create an empty binder object
@@ -198,11 +214,14 @@ public:
int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName);
int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName);
int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName);
+ int notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName);
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
int setPreferPowerEfficiency(bool enabled);
int reportActualWorkDuration(AWorkDuration* workDuration);
bool isJava();
+ status_t setNativeSurfaces(ANativeWindow** windows, int numWindows, ASurfaceControl** controls,
+ int numSurfaceControls);
private:
friend struct APerformanceHintManager;
@@ -231,7 +250,7 @@ private:
static int64_t sIDCounter GUARDED_BY(sHintMutex);
// The most recent set of thread IDs
std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex);
- std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex);
+ std::optional<hal::SessionConfig> mSessionConfig;
// Tracing helpers
void traceThreads(const std::vector<int32_t>& tids) REQUIRES(sHintMutex);
void tracePowerEfficient(bool powerEfficient);
@@ -310,7 +329,7 @@ APerformanceHintManager* APerformanceHintManager::create(std::shared_ptr<IHintMa
bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) {
mHintBudget =
- std::max(kMaxLoadHintsPerInterval,
+ std::min(kMaxLoadHintsPerInterval,
mHintBudget +
static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate);
mLastBudgetReplenish = now;
@@ -329,14 +348,12 @@ APerformanceHintSession* APerformanceHintManager::createSession(
ndk::ScopedAStatus ret;
hal::SessionConfig sessionConfig{.id = -1};
- SessionCreationConfig creationConfig{
+ ASessionCreationConfig creationConfig{{
.tids = std::vector<int32_t>(threadIds, threadIds + size),
.targetWorkDurationNanos = initialTargetWorkDurationNanos,
- };
+ }};
- return APerformanceHintManager::createSessionUsingConfig(static_cast<ASessionCreationConfig*>(
- &creationConfig),
- tag, isJava);
+ return APerformanceHintManager::createSessionUsingConfig(&creationConfig, tag, isJava);
}
APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig(
@@ -345,11 +362,29 @@ APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig(
hal::SessionConfig sessionConfig{.id = -1};
ndk::ScopedAStatus ret;
+ // Hold the tokens weakly until we actually need them,
+ // then promote them, then drop all strong refs after
+ if (!sessionCreationConfig->layers.empty()) {
+ for (auto&& layerIter = sessionCreationConfig->layers.begin();
+ layerIter != sessionCreationConfig->layers.end();) {
+ sp<IBinder> promoted = layerIter->promote();
+ if (promoted == nullptr) {
+ layerIter = sessionCreationConfig->layers.erase(layerIter);
+ } else {
+ sessionCreationConfig->layerTokens.push_back(
+ ndk::SpAIBinder(AIBinder_fromPlatformBinder(promoted.get())));
+ ++layerIter;
+ }
+ }
+ }
+
ret = mHintManager->createHintSessionWithConfig(mToken, tag,
*static_cast<SessionCreationConfig*>(
sessionCreationConfig),
&sessionConfig, &session);
+ sessionCreationConfig->layerTokens.clear();
+
if (!ret.isOk() || !session) {
ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage());
return nullptr;
@@ -566,6 +601,19 @@ int APerformanceHintSession::notifyWorkloadReset(bool cpu, bool gpu, const char*
return sendHints(hints, now, debugName);
}
+int APerformanceHintSession::notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName) {
+ std::vector<hal::SessionHint> hints(2);
+ hints.clear();
+ if (cpu) {
+ hints.push_back(hal::SessionHint::CPU_LOAD_SPIKE);
+ }
+ if (gpu) {
+ hints.push_back(hal::SessionHint::GPU_LOAD_SPIKE);
+ }
+ int64_t now = ::android::uptimeNanos();
+ return sendHints(hints, now, debugName);
+}
+
int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
if (size == 0) {
ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
@@ -679,6 +727,57 @@ int APerformanceHintSession::reportActualWorkDurationInternal(AWorkDuration* wor
return 0;
}
+status_t APerformanceHintSession::setNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls,
+ int numSurfaceControls) {
+ if (!mSessionConfig.has_value()) {
+ return ENOTSUP;
+ }
+
+ std::vector<sp<IBinder>> layerHandles;
+ APerformanceHintManager::layersFromNativeSurfaces<sp<IBinder>>(windows, numWindows, controls,
+ numSurfaceControls,
+ layerHandles);
+
+ std::vector<ndk::SpAIBinder> ndkLayerHandles;
+ for (auto&& handle : layerHandles) {
+ ndkLayerHandles.emplace_back(ndk::SpAIBinder(AIBinder_fromPlatformBinder(handle)));
+ }
+
+ mHintSession->associateToLayers(ndkLayerHandles);
+ return 0;
+}
+
+template <class T>
+void APerformanceHintManager::layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+ ASurfaceControl** controls,
+ int numSurfaceControls,
+ std::vector<T>& out) {
+ std::scoped_lock lock(sHintMutex);
+ if (windows != nullptr) {
+ std::vector<ANativeWindow*> windowVec(windows, windows + numWindows);
+ for (auto&& window : windowVec) {
+ Surface* surface = static_cast<Surface*>(window);
+ if (Surface::isValid(surface)) {
+ const sp<IBinder>& handle = surface->getSurfaceControlHandle();
+ if (handle != nullptr) {
+ out.push_back(handle);
+ }
+ }
+ }
+ }
+
+ if (controls != nullptr) {
+ std::vector<ASurfaceControl*> controlVec(controls, controls + numSurfaceControls);
+ for (auto&& aSurfaceControl : controlVec) {
+ SurfaceControl* control = reinterpret_cast<SurfaceControl*>(aSurfaceControl);
+ if (control->isValid()) {
+ out.push_back(control->getHandle());
+ }
+ }
+ }
+}
+
// ===================================== FMQ wrapper implementation
bool FMQWrapper::isActive() {
@@ -963,8 +1062,7 @@ APerformanceHintSession* APerformanceHint_createSessionFromJava(
hal::SessionTag::APP, true);
}
-APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env,
- jobject sessionObj) {
+APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env, jobject sessionObj) {
VALIDATE_PTR(env)
VALIDATE_PTR(sessionObj)
return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj);
@@ -1065,6 +1163,24 @@ int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool
return session->notifyWorkloadReset(cpu, gpu, debugName);
}
+int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool cpu, bool gpu,
+ const char* debugName) {
+ VALIDATE_PTR(session)
+ VALIDATE_PTR(debugName)
+ if (!useNewLoadHintBehavior()) {
+ return ENOTSUP;
+ }
+ return session->notifyWorkloadReset(cpu, gpu, debugName);
+}
+
+int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session,
+ ANativeWindow** nativeWindows, int nativeWindowsSize,
+ ASurfaceControl** surfaceControls, int surfaceControlsSize) {
+ VALIDATE_PTR(session)
+ return session->setNativeSurfaces(nativeWindows, nativeWindowsSize, surfaceControls,
+ surfaceControlsSize);
+}
+
AWorkDuration* AWorkDuration_create() {
return new AWorkDuration();
}
@@ -1180,6 +1296,11 @@ int ASessionCreationConfig_setGraphicsPipeline(ASessionCreationConfig* config, b
config->modesToEnable.push_back(hal::SessionMode::GRAPHICS_PIPELINE);
} else {
std::erase(config->modesToEnable, hal::SessionMode::GRAPHICS_PIPELINE);
+
+ // Remove automatic timing modes if we turn off GRAPHICS_PIPELINE,
+ // as it is a strict pre-requisite for these to run
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
}
return 0;
}
@@ -1197,3 +1318,48 @@ void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPe
void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) {
kForceNewHintBehavior = newBehavior;
}
+
+int ASessionCreationConfig_setNativeSurfaces(ASessionCreationConfig* config,
+ ANativeWindow** nativeWindows, int nativeWindowsSize,
+ ASurfaceControl** surfaceControls,
+ int surfaceControlsSize) {
+ VALIDATE_PTR(config)
+
+ APerformanceHintManager::layersFromNativeSurfaces<wp<IBinder>>(nativeWindows, nativeWindowsSize,
+ surfaceControls,
+ surfaceControlsSize,
+ config->layers);
+
+ if (config->layers.empty()) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+int ASessionCreationConfig_setUseAutoTiming(ASessionCreationConfig* _Nonnull config, bool cpu,
+ bool gpu) {
+ VALIDATE_PTR(config)
+ if ((cpu || gpu) && !config->hasMode(hal::SessionMode::GRAPHICS_PIPELINE)) {
+ ALOGE("Automatic timing is not supported unless graphics pipeline mode is enabled first");
+ return ENOTSUP;
+ }
+
+ if (config->hasMode(hal::SessionMode::AUTO_CPU)) {
+ if (!cpu) {
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+ }
+ } else if (cpu) {
+ config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_CPU));
+ }
+
+ if (config->hasMode(hal::SessionMode::AUTO_GPU)) {
+ if (!gpu) {
+ std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
+ }
+ } else if (gpu) {
+ config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_GPU));
+ }
+
+ return 0;
+}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index fc64e9b48f6d..4fe0b80f3951 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -64,6 +64,8 @@ static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_RGB) ==
static_cast<int>(android::gui::LutProperties::SamplingKey::RGB));
static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB) ==
static_cast<int>(android::gui::LutProperties::SamplingKey::MAX_RGB));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_CIE_Y) ==
+ static_cast<int>(android::gui::LutProperties::SamplingKey::CIE_Y));
Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
return reinterpret_cast<Transaction*>(aSurfaceTransaction);
@@ -794,28 +796,6 @@ void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* aSu
transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
}
-void ASurfaceTransaction_setFrameRateParams(
- ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
- ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
- CHECK_NOT_NULL(aSurfaceTransaction);
- CHECK_NOT_NULL(aSurfaceControl);
- Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
-
- if (desiredMaxRate < desiredMinRate) {
- ALOGW("desiredMaxRate must be greater than or equal to desiredMinRate");
- return;
- }
- // TODO(b/362798998): Fix plumbing to send modern params
- int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
- : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
- ? fixedSourceRate
- : desiredMinRate;
- transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
-}
-
void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl) {
CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp
new file mode 100644
index 000000000000..f3fa9f6836d5
--- /dev/null
+++ b/native/android/system_health.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/hardware/power/CpuHeadroomParams.h>
+#include <aidl/android/hardware/power/GpuHeadroomParams.h>
+#include <aidl/android/os/CpuHeadroomParamsInternal.h>
+#include <aidl/android/os/GpuHeadroomParamsInternal.h>
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/system_health.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+using namespace android;
+using namespace aidl::android::os;
+namespace hal = aidl::android::hardware::power;
+
+struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {};
+struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {};
+
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int CPU_HEADROOM_MAX_TID_COUNT = 5;
+
+struct ASystemHealthManager {
+public:
+ static ASystemHealthManager* getInstance();
+ ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager);
+ ASystemHealthManager() = delete;
+ ~ASystemHealthManager();
+ int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom);
+ int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom);
+ int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+ int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+
+private:
+ static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager);
+ std::shared_ptr<IHintManager> mHintManager;
+};
+
+ASystemHealthManager* ASystemHealthManager::getInstance() {
+ static std::once_flag creationFlag;
+ static ASystemHealthManager* instance = nullptr;
+ std::call_once(creationFlag, []() { instance = create(nullptr); });
+ return instance;
+}
+
+ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager)
+ : mHintManager(std::move(hintManager)) {}
+
+ASystemHealthManager::~ASystemHealthManager() {}
+
+ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) {
+ if (!hintManager) {
+ hintManager = IHintManager::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService("performance_hint")));
+ }
+ if (hintManager == nullptr) {
+ ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+ return nullptr;
+ }
+ return new ASystemHealthManager(hintManager);
+}
+
+ASystemHealthManager* ASystemHealth_acquireManager() {
+ return ASystemHealthManager::getInstance();
+}
+
+int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::CpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ CpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getCpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getCpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid ACpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getCpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ } else if (ret.getExceptionCode() == EX_SECURITY) {
+ return EPERM;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::GpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ GpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getGpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getGpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid AGpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getGpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
+ int tidsSize) {
+ LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
+ LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5",
+ __FUNCTION__);
+ params->tids.resize(tidsSize);
+ params->tids.clear();
+ for (int i = 0; i < tidsSize; ++i) {
+ LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
+ tids[i]);
+ params->tids[i] = tids[i];
+ }
+}
+
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
+ ACpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of ACpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::CpuHeadroomParams::CalculationType>(calculationType);
+}
+
+ACpuHeadroomCalculationType ACpuHeadroomParams_getCalculationType(
+ ACpuHeadroomParams* _Nonnull params) {
+ return static_cast<ACpuHeadroomCalculationType>(params->calculationType);
+}
+
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
+ AGpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of AGpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::GpuHeadroomParams::CalculationType>(calculationType);
+}
+
+AGpuHeadroomCalculationType AGpuHeadroomParams_getCalculationType(
+ AGpuHeadroomParams* _Nonnull params) {
+ return static_cast<AGpuHeadroomCalculationType>(params->calculationType);
+}
+
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create() {
+ return new ACpuHeadroomParams();
+}
+
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() {
+ return new AGpuHeadroomParams();
+}
+
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
+
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b00658052bde..c166e738ffb2 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -83,6 +83,7 @@ public:
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
+ MOCK_METHOD(ScopedAStatus, passSessionManagerBinder, (const SpAIBinder& sessionManager));
MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override));
};
@@ -99,6 +100,8 @@ public:
MOCK_METHOD(ScopedAStatus, close, (), (override));
MOCK_METHOD(ScopedAStatus, reportActualWorkDuration2,
(const ::std::vector<hal::WorkDuration>& workDurations), (override));
+ MOCK_METHOD(ScopedAStatus, associateToLayers,
+ (const std::vector<::ndk::SpAIBinder>& in_layerTokens), (override));
MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override));
};
@@ -296,6 +299,10 @@ TEST_F(PerformanceHintTest, TestSession) {
EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1));
result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
EXPECT_EQ(0, result);
+ EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_SPIKE))).Times(Exactly(1));
+ EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_SPIKE))).Times(Exactly(1));
+ result = APerformanceHint_notifyWorkloadSpike(session, true, true, "Test hint");
+ EXPECT_EQ(0, result);
result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1));
EXPECT_EQ(EINVAL, result);
diff --git a/native/android/tests/thermal/NativeThermalUnitTest.cpp b/native/android/tests/thermal/NativeThermalUnitTest.cpp
index 4e319fc41d7c..923ad011de41 100644
--- a/native/android/tests/thermal/NativeThermalUnitTest.cpp
+++ b/native/android/tests/thermal/NativeThermalUnitTest.cpp
@@ -77,12 +77,62 @@ public:
(override));
};
+struct HeadroomCallbackData {
+ void* data;
+ float headroom;
+ float forecast;
+ int32_t forecastSeconds;
+ const std::vector<float> thresholds;
+};
+
+struct StatusCallbackData {
+ void* data;
+ AThermalStatus status;
+};
+
+static std::optional<HeadroomCallbackData> headroomCalled1;
+static std::optional<HeadroomCallbackData> headroomCalled2;
+static std::optional<StatusCallbackData> statusCalled1;
+static std::optional<StatusCallbackData> statusCalled2;
+
+static std::vector<float> convertThresholds(const AThermalHeadroomThreshold* thresholds,
+ size_t size) {
+ std::vector<float> ret;
+ for (int i = 0; i < (int)size; i++) {
+ ret.emplace_back(thresholds[i].headroom);
+ }
+ return ret;
+};
+
+static void onHeadroomChange1(void* data, float headroom, float forecast, int32_t forecastSeconds,
+ const AThermalHeadroomThreshold* thresholds, size_t size) {
+ headroomCalled1.emplace(data, headroom, forecast, forecastSeconds,
+ convertThresholds(thresholds, size));
+}
+
+static void onHeadroomChange2(void* data, float headroom, float forecast, int32_t forecastSeconds,
+ const AThermalHeadroomThreshold* thresholds, size_t size) {
+ headroomCalled2.emplace(data, headroom, forecast, forecastSeconds,
+ convertThresholds(thresholds, size));
+}
+
+static void onStatusChange1(void* data, AThermalStatus status) {
+ statusCalled1.emplace(data, status);
+}
+static void onStatusChange2(void* data, AThermalStatus status) {
+ statusCalled2.emplace(data, status);
+}
+
class NativeThermalUnitTest : public Test {
public:
void SetUp() override {
mMockIThermalService = new StrictMock<MockIThermalService>();
AThermal_setIThermalServiceForTesting(mMockIThermalService);
mThermalManager = AThermal_acquireManager();
+ headroomCalled1.reset();
+ headroomCalled2.reset();
+ statusCalled1.reset();
+ statusCalled2.reset();
}
void TearDown() override {
@@ -117,9 +167,11 @@ TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholds) {
size_t size1;
ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds1, &size1));
checkThermalHeadroomThresholds(expected, thresholds1, size1);
- // following calls should be cached
- EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_)).Times(0);
-
+ // following calls should not be cached
+ expected = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+ EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(expected), Return(Status())));
const AThermalHeadroomThreshold* thresholds2 = nullptr;
size_t size2;
ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds2, &size2));
@@ -164,3 +216,248 @@ TEST_F(NativeThermalUnitTest, TestGetThermalHeadroomThresholdsFailedWithNonEmpty
ASSERT_EQ(EINVAL, AThermal_getThermalHeadroomThresholds(mThermalManager, &initialized, &size));
delete[] initialized;
}
+
+TEST_F(NativeThermalUnitTest, TestRegisterThermalHeadroomListener) {
+ EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+ .Times(Exactly(2))
+ .WillOnce(Return(
+ Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+ float data1 = 1.0f;
+ float data2 = 2.0f;
+ ASSERT_EQ(EPIPE,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+ ASSERT_EQ(EPIPE,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+
+ // verify only 1 service call to register a global listener
+ sp<IThermalHeadroomListener> capturedServiceListener;
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalHeadroomListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(0,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+ ASSERT_EQ(EINVAL,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+ ASSERT_EQ(0,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+ const ::std::vector<float> thresholds = {0.1f, 0.2f};
+ capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+ ASSERT_TRUE(headroomCalled1.has_value());
+ EXPECT_EQ(headroomCalled1->data, &data1);
+ EXPECT_EQ(headroomCalled1->headroom, 0.1f);
+ EXPECT_EQ(headroomCalled1->forecast, 0.3f);
+ EXPECT_EQ(headroomCalled1->forecastSeconds, 20);
+ EXPECT_EQ(headroomCalled1->thresholds, thresholds);
+ ASSERT_TRUE(headroomCalled2.has_value());
+ EXPECT_EQ(headroomCalled2->data, &data2);
+ EXPECT_EQ(headroomCalled2->headroom, 0.1f);
+ EXPECT_EQ(headroomCalled2->forecast, 0.3f);
+ EXPECT_EQ(headroomCalled2->forecastSeconds, 20);
+ EXPECT_EQ(headroomCalled2->thresholds, thresholds);
+
+ // after test finished the global service listener should be unregistered
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestUnregisterThermalHeadroomListener) {
+ sp<IThermalHeadroomListener> capturedServiceListener;
+ EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalHeadroomListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ float data1 = 1.0f;
+ float data2 = 2.0f;
+ ASSERT_EQ(0,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+ ASSERT_EQ(0,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+ capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, {});
+ ASSERT_TRUE(headroomCalled1.has_value());
+ ASSERT_TRUE(headroomCalled2.has_value());
+
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+
+ // callback 1 should be unregistered and callback 2 unregistration should fail due to service
+ // listener unregistration call failure
+ ASSERT_EQ(0,
+ AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1,
+ &data1));
+ ASSERT_EQ(EPIPE,
+ AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2,
+ &data2));
+ // verify only callback 2 is called after callback 1 is unregistered
+ std::vector<float> thresholds = {0.1f, 0.2f};
+ headroomCalled1.reset();
+ headroomCalled2.reset();
+ capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+ ASSERT_TRUE(!headroomCalled1.has_value());
+ ASSERT_TRUE(headroomCalled2.has_value());
+
+ // verify only 1 service call to unregister global service listener
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::Invoke([](const sp<IThermalHeadroomListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(EINVAL,
+ AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1,
+ &data1));
+ ASSERT_EQ(0,
+ AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2,
+ &data2));
+ // verify neither callback is called after global service listener is unregistered
+ headroomCalled1.reset();
+ headroomCalled2.reset();
+ capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+ ASSERT_TRUE(!headroomCalled1.has_value());
+ ASSERT_TRUE(!headroomCalled2.has_value());
+
+ // verify adding a new callback will still work
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalHeadroomListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(0,
+ AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+ headroomCalled1.reset();
+ capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+ ASSERT_TRUE(headroomCalled1.has_value());
+ EXPECT_EQ(headroomCalled1->data, &data1);
+ EXPECT_EQ(headroomCalled1->headroom, 0.1f);
+ EXPECT_EQ(headroomCalled1->forecast, 0.3f);
+ EXPECT_EQ(headroomCalled1->forecastSeconds, 20);
+ EXPECT_EQ(headroomCalled1->thresholds, thresholds);
+
+ // after test finished the global service listener should be unregistered
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestRegisterThermalStatusListener) {
+ EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+ .Times(Exactly(2))
+ .WillOnce(Return(
+ Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+ int data1 = 1;
+ int data2 = 2;
+ ASSERT_EQ(EPIPE,
+ AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(EPIPE,
+ AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+ // verify only 1 service call to register a global listener
+ sp<IThermalStatusListener> capturedServiceListener;
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalStatusListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(EINVAL,
+ AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+ capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(statusCalled1.has_value());
+ EXPECT_EQ(statusCalled1->data, &data1);
+ EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(statusCalled2.has_value());
+ EXPECT_EQ(statusCalled2->data, &data2);
+ EXPECT_EQ(statusCalled2->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+
+ // after test finished the callback should be unregistered
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestUnregisterThermalStatusListener) {
+ sp<IThermalStatusListener> capturedServiceListener;
+ EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalStatusListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ int data1 = 1;
+ int data2 = 2;
+ ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+ capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(statusCalled1.has_value());
+ ASSERT_TRUE(statusCalled2.has_value());
+
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(Return(
+ Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+ // callback 1 should be unregistered and callback 2 unregistration should fail due to service
+ // listener unregistration call failure
+ ASSERT_EQ(0,
+ AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(EPIPE,
+ AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+ // verify only callback 2 is called after callback 1 is unregistered
+ statusCalled1.reset();
+ statusCalled2.reset();
+ capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(!statusCalled1.has_value());
+ ASSERT_TRUE(statusCalled2.has_value());
+
+ // verify only 1 service call to unregister global service listener
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::Invoke([](const sp<IThermalStatusListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(EINVAL,
+ AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ ASSERT_EQ(0,
+ AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+ // verify neither callback is called after global service listener is unregistered
+ statusCalled1.reset();
+ statusCalled2.reset();
+ capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(!statusCalled1.has_value());
+ ASSERT_TRUE(!statusCalled2.has_value());
+
+ // verify adding a new callback will still work
+ Mock::VerifyAndClearExpectations(mMockIThermalService);
+ EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+ testing::Invoke([](const sp<IThermalStatusListener>&,
+ bool* aidl_return) { *aidl_return = true; }),
+ Return(Status::ok())));
+ ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+ statusCalled1.reset();
+ capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+ ASSERT_TRUE(statusCalled1.has_value());
+ EXPECT_EQ(statusCalled1->data, &data1);
+ EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+
+ // after test finished the global service listener should be unregistered
+ EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+ .Times(Exactly(1))
+ .WillOnce(Return(binder::Status::ok()));
+}
diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp
index f7a3537d3f4a..cefcaf7766bb 100644
--- a/native/android/thermal.cpp
+++ b/native/android/thermal.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "thermal"
#include <android-base/thread_annotations.h>
+#include <android/os/BnThermalHeadroomListener.h>
#include <android/os/BnThermalStatusListener.h>
#include <android/os/IThermalService.h>
#include <android/thermal.h>
@@ -33,10 +34,10 @@ using android::sp;
using namespace android;
using namespace android::os;
-struct ThermalServiceListener : public BnThermalStatusListener {
+struct ThermalServiceStatusListener : public BnThermalStatusListener {
public:
virtual binder::Status onStatusChange(int32_t status) override;
- ThermalServiceListener(AThermalManager *manager) {
+ ThermalServiceStatusListener(AThermalManager *manager) {
mMgr = manager;
}
@@ -44,11 +45,29 @@ private:
AThermalManager *mMgr;
};
-struct ListenerCallback {
+struct ThermalServiceHeadroomListener : public BnThermalHeadroomListener {
+public:
+ virtual binder::Status onHeadroomChange(float headroom, float forecastHeadroom,
+ int32_t forecastSeconds,
+ const ::std::vector<float> &thresholds) override;
+ ThermalServiceHeadroomListener(AThermalManager *manager) {
+ mMgr = manager;
+ }
+
+private:
+ AThermalManager *mMgr;
+};
+
+struct StatusListenerCallback {
AThermal_StatusCallback callback;
void* data;
};
+struct HeadroomListenerCallback {
+ AThermal_HeadroomCallback callback;
+ void *data;
+};
+
static IThermalService *gIThermalServiceForTesting = nullptr;
struct AThermalManager {
@@ -57,30 +76,44 @@ public:
AThermalManager() = delete;
~AThermalManager();
status_t notifyStateChange(int32_t status);
+ status_t notifyHeadroomChange(float headroom, float forecastHeadroom, int32_t forecastSeconds,
+ const ::std::vector<float> &thresholds);
status_t getCurrentThermalStatus(int32_t *status);
- status_t addListener(AThermal_StatusCallback, void *data);
- status_t removeListener(AThermal_StatusCallback, void *data);
+ status_t addStatusListener(AThermal_StatusCallback, void *data);
+ status_t removeStatusListener(AThermal_StatusCallback, void *data);
status_t getThermalHeadroom(int32_t forecastSeconds, float *result);
status_t getThermalHeadroomThresholds(const AThermalHeadroomThreshold **, size_t *size);
+ status_t addHeadroomListener(AThermal_HeadroomCallback, void *data);
+ status_t removeHeadroomListener(AThermal_HeadroomCallback, void *data);
private:
AThermalManager(sp<IThermalService> service);
sp<IThermalService> mThermalSvc;
- std::mutex mListenerMutex;
- sp<ThermalServiceListener> mServiceListener GUARDED_BY(mListenerMutex);
- std::vector<ListenerCallback> mListeners GUARDED_BY(mListenerMutex);
- std::mutex mThresholdsMutex;
- const AThermalHeadroomThreshold *mThresholds = nullptr; // GUARDED_BY(mThresholdsMutex)
- size_t mThresholdsCount GUARDED_BY(mThresholdsMutex);
+ std::mutex mStatusListenerMutex;
+ sp<ThermalServiceStatusListener> mServiceStatusListener GUARDED_BY(mStatusListenerMutex);
+ std::vector<StatusListenerCallback> mStatusListeners GUARDED_BY(mStatusListenerMutex);
+
+ std::mutex mHeadroomListenerMutex;
+ sp<ThermalServiceHeadroomListener> mServiceHeadroomListener GUARDED_BY(mHeadroomListenerMutex);
+ std::vector<HeadroomListenerCallback> mHeadroomListeners GUARDED_BY(mHeadroomListenerMutex);
};
-binder::Status ThermalServiceListener::onStatusChange(int32_t status) {
+binder::Status ThermalServiceStatusListener::onStatusChange(int32_t status) {
if (mMgr != nullptr) {
mMgr->notifyStateChange(status);
}
return binder::Status::ok();
}
+binder::Status ThermalServiceHeadroomListener::onHeadroomChange(
+ float headroom, float forecastHeadroom, int32_t forecastSeconds,
+ const ::std::vector<float> &thresholds) {
+ if (mMgr != nullptr) {
+ mMgr->notifyHeadroomChange(headroom, forecastHeadroom, forecastSeconds, thresholds);
+ }
+ return binder::Status::ok();
+}
+
AThermalManager* AThermalManager::createAThermalManager() {
if (gIThermalServiceForTesting) {
return new AThermalManager(gIThermalServiceForTesting);
@@ -96,97 +129,183 @@ AThermalManager* AThermalManager::createAThermalManager() {
}
AThermalManager::AThermalManager(sp<IThermalService> service)
- : mThermalSvc(std::move(service)), mServiceListener(nullptr) {}
+ : mThermalSvc(std::move(service)),
+ mServiceStatusListener(nullptr),
+ mServiceHeadroomListener(nullptr) {}
AThermalManager::~AThermalManager() {
{
- std::scoped_lock<std::mutex> listenerLock(mListenerMutex);
- mListeners.clear();
- if (mServiceListener != nullptr) {
+ std::scoped_lock<std::mutex> listenerLock(mStatusListenerMutex);
+ mStatusListeners.clear();
+ if (mServiceStatusListener != nullptr) {
bool success = false;
- mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success);
- mServiceListener = nullptr;
+ mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ mServiceStatusListener = nullptr;
+ }
+ }
+ {
+ std::scoped_lock<std::mutex> headroomListenerLock(mHeadroomListenerMutex);
+ mHeadroomListeners.clear();
+ if (mServiceHeadroomListener != nullptr) {
+ bool success = false;
+ mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
+ mServiceHeadroomListener = nullptr;
}
}
- std::scoped_lock<std::mutex> lock(mThresholdsMutex);
- delete[] mThresholds;
}
status_t AThermalManager::notifyStateChange(int32_t status) {
- std::scoped_lock<std::mutex> lock(mListenerMutex);
+ std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
AThermalStatus thermalStatus = static_cast<AThermalStatus>(status);
- for (auto listener : mListeners) {
+ for (auto listener : mStatusListeners) {
listener.callback(listener.data, thermalStatus);
}
return OK;
}
-status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *data) {
- std::scoped_lock<std::mutex> lock(mListenerMutex);
+status_t AThermalManager::notifyHeadroomChange(float headroom, float forecastHeadroom,
+ int32_t forecastSeconds,
+ const ::std::vector<float> &thresholds) {
+ std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+ size_t thresholdsCount = thresholds.size();
+ auto t = new AThermalHeadroomThreshold[thresholdsCount];
+ for (int i = 0; i < (int)thresholdsCount; i++) {
+ t[i].headroom = thresholds[i];
+ t[i].thermalStatus = static_cast<AThermalStatus>(i);
+ }
+ for (auto listener : mHeadroomListeners) {
+ listener.callback(listener.data, headroom, forecastHeadroom, forecastSeconds, t,
+ thresholdsCount);
+ }
+ delete[] t;
+ return OK;
+}
+
+status_t AThermalManager::addStatusListener(AThermal_StatusCallback callback, void *data) {
+ std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
if (callback == nullptr) {
// Callback can not be nullptr
return EINVAL;
}
- for (const auto& cb : mListeners) {
+ for (const auto &cb : mStatusListeners) {
// Don't re-add callbacks.
if (callback == cb.callback && data == cb.data) {
return EINVAL;
}
}
- mListeners.emplace_back(ListenerCallback{callback, data});
- if (mServiceListener != nullptr) {
+ if (mServiceStatusListener != nullptr) {
+ mStatusListeners.emplace_back(StatusListenerCallback{callback, data});
return OK;
}
bool success = false;
- mServiceListener = new ThermalServiceListener(this);
- if (mServiceListener == nullptr) {
+ mServiceStatusListener = new ThermalServiceStatusListener(this);
+ if (mServiceStatusListener == nullptr) {
return ENOMEM;
}
- auto ret = mThermalSvc->registerThermalStatusListener(mServiceListener, &success);
+ auto ret = mThermalSvc->registerThermalStatusListener(mServiceStatusListener, &success);
if (!success || !ret.isOk()) {
+ mServiceStatusListener = nullptr;
ALOGE("Failed in registerThermalStatusListener %d", success);
if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
return EPERM;
}
return EPIPE;
}
+ mStatusListeners.emplace_back(StatusListenerCallback{callback, data});
return OK;
}
-status_t AThermalManager::removeListener(AThermal_StatusCallback callback, void *data) {
- std::scoped_lock<std::mutex> lock(mListenerMutex);
+status_t AThermalManager::removeStatusListener(AThermal_StatusCallback callback, void *data) {
+ std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
- auto it = std::remove_if(mListeners.begin(),
- mListeners.end(),
- [&](const ListenerCallback& cb) {
- return callback == cb.callback &&
- data == cb.data;
+ auto it = std::remove_if(mStatusListeners.begin(), mStatusListeners.end(),
+ [&](const StatusListenerCallback &cb) {
+ return callback == cb.callback && data == cb.data;
});
- if (it == mListeners.end()) {
+ if (it == mStatusListeners.end()) {
// If the listener and data pointer were not previously added.
return EINVAL;
}
- mListeners.erase(it, mListeners.end());
+ if (mServiceStatusListener == nullptr || mStatusListeners.size() > 1) {
+ mStatusListeners.erase(it, mStatusListeners.end());
+ return OK;
+ }
- if (!mListeners.empty()) {
+ bool success = false;
+ auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in unregisterThermalStatusListener %d", success);
+ if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
+ return EPERM;
+ }
+ return EPIPE;
+ }
+ mServiceStatusListener = nullptr;
+ mStatusListeners.erase(it, mStatusListeners.end());
+ return OK;
+}
+
+status_t AThermalManager::addHeadroomListener(AThermal_HeadroomCallback callback, void *data) {
+ std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+ if (callback == nullptr) {
+ return EINVAL;
+ }
+ for (const auto &cb : mHeadroomListeners) {
+ if (callback == cb.callback && data == cb.data) {
+ return EINVAL;
+ }
+ }
+
+ if (mServiceHeadroomListener != nullptr) {
+ mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data});
return OK;
}
- if (mServiceListener == nullptr) {
+ bool success = false;
+ mServiceHeadroomListener = new ThermalServiceHeadroomListener(this);
+ if (mServiceHeadroomListener == nullptr) {
+ return ENOMEM;
+ }
+ auto ret = mThermalSvc->registerThermalHeadroomListener(mServiceHeadroomListener, &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in registerThermalHeadroomListener %d", success);
+ mServiceHeadroomListener = nullptr;
+ if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
+ return EPERM;
+ }
+ return EPIPE;
+ }
+ mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data});
+ return OK;
+}
+
+status_t AThermalManager::removeHeadroomListener(AThermal_HeadroomCallback callback, void *data) {
+ std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+
+ auto it = std::remove_if(mHeadroomListeners.begin(), mHeadroomListeners.end(),
+ [&](const HeadroomListenerCallback &cb) {
+ return callback == cb.callback && data == cb.data;
+ });
+ if (it == mHeadroomListeners.end()) {
+ return EINVAL;
+ }
+ if (mServiceHeadroomListener == nullptr || mHeadroomListeners.size() > 1) {
+ mHeadroomListeners.erase(it, mHeadroomListeners.end());
return OK;
}
bool success = false;
- auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success);
+ auto ret = mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
if (!success || !ret.isOk()) {
- ALOGE("Failed in unregisterThermalStatusListener %d", success);
+ ALOGE("Failed in unregisterThermalHeadroomListener %d", success);
if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
return EPERM;
}
return EPIPE;
}
- mServiceListener = nullptr;
+ mServiceHeadroomListener = nullptr;
+ mHeadroomListeners.erase(it, mHeadroomListeners.end());
return OK;
}
@@ -216,61 +335,36 @@ status_t AThermalManager::getThermalHeadroom(int32_t forecastSeconds, float *res
status_t AThermalManager::getThermalHeadroomThresholds(const AThermalHeadroomThreshold **result,
size_t *size) {
- std::scoped_lock<std::mutex> lock(mThresholdsMutex);
- if (mThresholds == nullptr) {
- auto thresholds = std::make_unique<std::vector<float>>();
- binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get());
- if (!ret.isOk()) {
- if (ret.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
- // feature is not enabled
- return ENOSYS;
- }
- return EPIPE;
- }
- mThresholdsCount = thresholds->size();
- auto t = new AThermalHeadroomThreshold[mThresholdsCount];
- for (int i = 0; i < (int)mThresholdsCount; i++) {
- t[i].headroom = (*thresholds)[i];
- t[i].thermalStatus = static_cast<AThermalStatus>(i);
+ auto thresholds = std::make_unique<std::vector<float>>();
+ binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get());
+ if (!ret.isOk()) {
+ if (ret.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ // feature is not enabled
+ return ENOSYS;
}
- mThresholds = t;
+ return EPIPE;
+ }
+ size_t thresholdsCount = thresholds->size();
+ auto t = new AThermalHeadroomThreshold[thresholdsCount];
+ for (int i = 0; i < (int)thresholdsCount; i++) {
+ t[i].headroom = (*thresholds)[i];
+ t[i].thermalStatus = static_cast<AThermalStatus>(i);
}
- *size = mThresholdsCount;
- *result = mThresholds;
+ *size = thresholdsCount;
+ *result = t;
return OK;
}
-/**
- * Acquire an instance of the thermal manager. This must be freed using
- * {@link AThermal_releaseManager}.
- *
- * @return manager instance on success, nullptr on failure.
- */
AThermalManager* AThermal_acquireManager() {
auto manager = AThermalManager::createAThermalManager();
return manager;
}
-/**
- * Release the thermal manager pointer acquired by
- * {@link AThermal_acquireManager}.
- *
- * @param manager The manager to be released.
- *
- */
void AThermal_releaseManager(AThermalManager *manager) {
delete manager;
}
-/**
- * Gets the current thermal status.
- *
- * @param manager The manager instance to use to query the thermal status,
- * acquired by {@link AThermal_acquireManager}.
- *
- * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
-*/
AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) {
int32_t status = 0;
status_t ret = manager->getCurrentThermalStatus(&status);
@@ -280,59 +374,16 @@ AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) {
return static_cast<AThermalStatus>(status);
}
-/**
- * Register the thermal status listener for thermal status change.
- *
- * @param manager The manager instance to use to register.
- * acquired by {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
- * @param data The data pointer to be passed when callback is called.
- *
- * @return 0 on success
- * EINVAL if the listener and data pointer were previously added and not removed.
- * EPERM if the required permission is not held.
- * EPIPE if communication with the system service has failed.
- */
int AThermal_registerThermalStatusListener(AThermalManager *manager,
- AThermal_StatusCallback callback, void *data) {
- return manager->addListener(callback, data);
+ AThermal_StatusCallback callback, void *data) {
+ return manager->addStatusListener(callback, data);
}
-/**
- * Unregister the thermal status listener previously resgistered.
- *
- * @param manager The manager instance to use to unregister.
- * acquired by {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
- * @param data The data pointer to be passed when callback is called.
- *
- * @return 0 on success
- * EINVAL if the listener and data pointer were not previously added.
- * EPERM if the required permission is not held.
- * EPIPE if communication with the system service has failed.
- */
int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
- AThermal_StatusCallback callback, void *data) {
- return manager->removeListener(callback, data);
+ AThermal_StatusCallback callback, void *data) {
+ return manager->removeStatusListener(callback, data);
}
-/**
- * Provides an estimate of how much thermal headroom the device currently has
- * before hitting severe throttling.
- *
- * Note that this only attempts to track the headroom of slow-moving sensors,
- * such as the skin temperature sensor. This means that there is no benefit to
- * calling this function more frequently than about once per second, and attempts
- * to call significantly more frequently may result in the function returning {@code NaN}.
- *
- * See also PowerManager#getThermalHeadroom.
- *
- * @param manager The manager instance to use
- * @param forecastSeconds how many seconds in the future to forecast
- * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling
- * threshold. Returns NaN if the device does not support this functionality or if
- * this function is called significantly faster than once per second.
- */
float AThermal_getThermalHeadroom(AThermalManager *manager, int forecastSeconds) {
float result = 0.0f;
status_t ret = manager->getThermalHeadroom(forecastSeconds, &result);
@@ -354,3 +405,13 @@ int AThermal_getThermalHeadroomThresholds(AThermalManager *manager,
void AThermal_setIThermalServiceForTesting(void *iThermalService) {
gIThermalServiceForTesting = static_cast<IThermalService *>(iThermalService);
}
+
+int AThermal_registerThermalHeadroomListener(AThermalManager *manager,
+ AThermal_HeadroomCallback callback, void *data) {
+ return manager->addHeadroomListener(callback, data);
+}
+
+int AThermal_unregisterThermalHeadroomListener(AThermalManager *manager,
+ AThermal_HeadroomCallback callback, void *data) {
+ return manager->removeHeadroomListener(callback, data);
+}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3ed9b7667be7..e97b15d3b926 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -124,6 +124,7 @@ package android.nfc {
@FlaggedApi("android.nfc.nfc_oem_extension") public abstract class NfcRoutingTableEntry {
method public int getNfceeId();
+ method public int getRouteType();
method public int getType();
field public static final int TYPE_AID = 0; // 0x0
field public static final int TYPE_PROTOCOL = 1; // 0x1
diff --git a/nfc/java/android/nfc/Entry.java b/nfc/java/android/nfc/Entry.java
index 49d0f10dbfce..aa5ba58e7179 100644
--- a/nfc/java/android/nfc/Entry.java
+++ b/nfc/java/android/nfc/Entry.java
@@ -25,11 +25,13 @@ public final class Entry implements Parcelable {
private final byte mType;
private final byte mNfceeId;
private final String mEntry;
+ private final String mRoutingType;
- public Entry(String entry, byte type, byte nfceeId) {
+ public Entry(String entry, byte type, byte nfceeId, String routingType) {
mEntry = entry;
mType = type;
mNfceeId = nfceeId;
+ mRoutingType = routingType;
}
public byte getType() {
@@ -44,6 +46,10 @@ public final class Entry implements Parcelable {
return mEntry;
}
+ public String getRoutingType() {
+ return mRoutingType;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -53,6 +59,7 @@ public final class Entry implements Parcelable {
this.mEntry = in.readString();
this.mNfceeId = in.readByte();
this.mType = in.readByte();
+ this.mRoutingType = in.readString();
}
public static final @NonNull Parcelable.Creator<Entry> CREATOR =
@@ -73,5 +80,6 @@ public final class Entry implements Parcelable {
dest.writeString(mEntry);
dest.writeByte(mNfceeId);
dest.writeByte(mType);
+ dest.writeString(mRoutingType);
}
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index f78161e7cad0..fb11875ec7d7 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -887,18 +887,22 @@ public final class NfcOemExtension {
switch (entry.getType()) {
case TYPE_TECHNOLOGY -> result.add(
new RoutingTableTechnologyEntry(entry.getNfceeId(),
- RoutingTableTechnologyEntry.techStringToInt(entry.getEntry()))
+ RoutingTableTechnologyEntry.techStringToInt(entry.getEntry()),
+ routeStringToInt(entry.getRoutingType()))
);
case TYPE_PROTOCOL -> result.add(
new RoutingTableProtocolEntry(entry.getNfceeId(),
- RoutingTableProtocolEntry.protocolStringToInt(entry.getEntry()))
+ RoutingTableProtocolEntry.protocolStringToInt(entry.getEntry()),
+ routeStringToInt(entry.getRoutingType()))
);
case TYPE_AID -> result.add(
- new RoutingTableAidEntry(entry.getNfceeId(), entry.getEntry())
+ new RoutingTableAidEntry(entry.getNfceeId(), entry.getEntry(),
+ routeStringToInt(entry.getRoutingType()))
);
case TYPE_SYSTEMCODE -> result.add(
new RoutingTableSystemCodeEntry(entry.getNfceeId(),
- entry.getEntry().getBytes(StandardCharsets.UTF_8))
+ entry.getEntry().getBytes(StandardCharsets.UTF_8),
+ routeStringToInt(entry.getRoutingType()))
);
}
}
diff --git a/nfc/java/android/nfc/NfcRoutingTableEntry.java b/nfc/java/android/nfc/NfcRoutingTableEntry.java
index c2cbbede9b75..4153779a8ba2 100644
--- a/nfc/java/android/nfc/NfcRoutingTableEntry.java
+++ b/nfc/java/android/nfc/NfcRoutingTableEntry.java
@@ -19,6 +19,7 @@ package android.nfc;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -35,6 +36,7 @@ import java.lang.annotation.RetentionPolicy;
public abstract class NfcRoutingTableEntry {
private final int mNfceeId;
private final int mType;
+ private final int mRouteType;
/**
* AID routing table type.
@@ -67,9 +69,11 @@ public abstract class NfcRoutingTableEntry {
public @interface RoutingTableType {}
/** @hide */
- protected NfcRoutingTableEntry(int nfceeId, @RoutingTableType int type) {
+ protected NfcRoutingTableEntry(int nfceeId, @RoutingTableType int type,
+ @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
mNfceeId = nfceeId;
mType = type;
+ mRouteType = routeType;
}
/**
@@ -88,4 +92,14 @@ public abstract class NfcRoutingTableEntry {
public int getType() {
return mType;
}
+
+ /**
+ * Get the route type of this entry.
+ * @return an integer defined in
+ * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+ */
+ @CardEmulation.ProtocolAndTechnologyRoute
+ public int getRouteType() {
+ return mRouteType;
+ }
}
diff --git a/nfc/java/android/nfc/RoutingTableAidEntry.java b/nfc/java/android/nfc/RoutingTableAidEntry.java
index bf697d662bd6..be94f9fc117c 100644
--- a/nfc/java/android/nfc/RoutingTableAidEntry.java
+++ b/nfc/java/android/nfc/RoutingTableAidEntry.java
@@ -18,6 +18,7 @@ package android.nfc;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
/**
* Represents an Application ID (AID) entry in current routing table.
@@ -29,8 +30,9 @@ public class RoutingTableAidEntry extends NfcRoutingTableEntry {
private final String mValue;
/** @hide */
- public RoutingTableAidEntry(int nfceeId, String value) {
- super(nfceeId, TYPE_AID);
+ public RoutingTableAidEntry(int nfceeId, String value,
+ @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+ super(nfceeId, TYPE_AID, routeType);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableProtocolEntry.java b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
index 536de4d7430e..a68d8c167865 100644
--- a/nfc/java/android/nfc/RoutingTableProtocolEntry.java
+++ b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
@@ -18,6 +18,7 @@ package android.nfc;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -96,8 +97,9 @@ public class RoutingTableProtocolEntry extends NfcRoutingTableEntry {
private final @ProtocolValue int mValue;
/** @hide */
- public RoutingTableProtocolEntry(int nfceeId, @ProtocolValue int value) {
- super(nfceeId, TYPE_PROTOCOL);
+ public RoutingTableProtocolEntry(int nfceeId, @ProtocolValue int value,
+ @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+ super(nfceeId, TYPE_PROTOCOL, routeType);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
index f61892d31668..06cc0a5f26f1 100644
--- a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
+++ b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
@@ -18,6 +18,7 @@ package android.nfc;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
/**
* Represents a system code entry in current routing table, where system codes are two-byte values
@@ -31,8 +32,9 @@ public class RoutingTableSystemCodeEntry extends NfcRoutingTableEntry {
private final byte[] mValue;
/** @hide */
- public RoutingTableSystemCodeEntry(int nfceeId, byte[] value) {
- super(nfceeId, TYPE_SYSTEM_CODE);
+ public RoutingTableSystemCodeEntry(int nfceeId, byte[] value,
+ @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+ super(nfceeId, TYPE_SYSTEM_CODE, routeType);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
index 2dbc94232b0b..86239ce7a6b2 100644
--- a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
+++ b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
@@ -18,6 +18,7 @@ package android.nfc;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -77,8 +78,9 @@ public class RoutingTableTechnologyEntry extends NfcRoutingTableEntry {
private final @TechnologyValue int mValue;
/** @hide */
- public RoutingTableTechnologyEntry(int nfceeId, @TechnologyValue int value) {
- super(nfceeId, TYPE_TECHNOLOGY);
+ public RoutingTableTechnologyEntry(int nfceeId, @TechnologyValue int value,
+ @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+ super(nfceeId, TYPE_TECHNOLOGY, routeType);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 4f601f0704b4..db1f6a2bb3b1 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -107,7 +107,7 @@ import java.util.List;
* &lt;intent-filter&gt;
* &lt;action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/&gt;
* &lt;/intent-filter&gt;
- * &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ * &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/apduservice"/&gt;
* &lt;/service&gt;</pre>
*
* This meta-data tag points to an apduservice.xml file.
diff --git a/nfc/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
index 2286e8476d94..8d8a17270523 100644
--- a/nfc/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
@@ -96,7 +96,7 @@ import android.os.IBinder;
* &lt;intent-filter&gt;
* &lt;action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/&gt;
* &lt;/intent-filter&gt;
- * &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ * &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/apduservice"/&gt;
* &lt;/service&gt;</pre>
*
* This meta-data tag points to an apduservice.xml file.
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
index dd7b03de47cd..67b496e0baf3 100644
--- a/nfc/lint-baseline.xml
+++ b/nfc/lint-baseline.xml
@@ -2,215 +2,6 @@
<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
- errorLine1=" AidGroup aidGroup = new AidGroup(aids, category);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="377"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
- errorLine1=" return (group != null ? group.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="537"
- column="43"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
- errorLine1=" return (group != null ? group.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="547"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="714"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="724"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
- errorLine1=" if (!serviceInfo.isOnHost()) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="755"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="756"
- column="40"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=' "OffHost" : serviceInfo.getOffHostSecureElement();'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="757"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
- errorLine1=" if (!serviceInfo.isOnHost()) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="772"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="773"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=' "Offhost" : serviceInfo.getOffHostSecureElement();'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="774"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="798"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="808"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="1032"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="1066"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" resumed = activity.isResumed();"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
- line="124"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
- line="2457"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
- line="315"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
- line="351"
- column="23"/>
- </issue>
-
- <issue
id="FlaggedApi"
message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)"
errorLine1=" mNfcOemExtension = new NfcOemExtension(mContext, this);"
@@ -287,4 +78,4 @@
column="44"/>
</issue>
-</issues> \ No newline at end of file
+</issues>
diff --git a/nfc/tests/src/android/nfc/NdefRecordTest.java b/nfc/tests/src/android/nfc/NdefRecordTest.java
new file mode 100644
index 000000000000..044c67448329
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefRecordTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NdefRecordTest {
+
+ @Test
+ public void testNdefRecordConstructor() throws FormatException {
+ NdefRecord applicationRecord = NdefRecord
+ .createApplicationRecord("com.android.test");
+ NdefRecord ndefRecord = new NdefRecord(applicationRecord.toByteArray());
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.toByteArray().length).isGreaterThan(0);
+ assertThat(ndefRecord.getType()).isEqualTo("android.com:pkg".getBytes());
+ assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+ }
+
+ @Test
+ public void testCreateExternal() {
+ NdefRecord ndefRecord = NdefRecord.createExternal("test",
+ "android.com:pkg", "com.android.test".getBytes());
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getType()).isEqualTo("test:android.com:pkg".getBytes());
+ assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+ }
+
+ @Test
+ public void testCreateUri() {
+ NdefRecord ndefRecord = NdefRecord.createUri("http://www.example.com");
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN);
+ assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_URI);
+ }
+
+ @Test
+ public void testCreateMime() {
+ NdefRecord ndefRecord = NdefRecord.createMime("text/plain", "example".getBytes());
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_MIME_MEDIA);
+ }
+
+ @Test
+ public void testCreateTextRecord() {
+ String languageCode = Locale.getDefault().getLanguage();
+ NdefRecord ndefRecord = NdefRecord.createTextRecord(languageCode, "testdata");
+ assertThat(ndefRecord).isNotNull();
+ assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN);
+ assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_TEXT);
+ }
+
+}
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index e71597a27a39..3916bf3df73d 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,7 @@ aidl_interface {
backend: {
java: {
sdk_version: "module_current",
+ min_sdk_version: "35", // Make it 36 once available.
apex_available: [
"//apex_available:platform",
"com.android.nfcservices",
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 8f9730a9651b..6fdd73a37db5 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -50,6 +50,7 @@
android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
android:label="@string/action_bar_label"
android:exported="true"
+ android:enableOnBackInvokedCallback="false"
android:permission="android.permission.MODIFY_PHONE_STATE"
android:theme="@style/AppTheme"
android:configChanges="keyboardHidden|orientation|screenSize">
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 2ba93f15f7fc..560e7519de5e 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@ import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecov
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -91,6 +92,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -336,20 +338,27 @@ public class PackageWatchdog {
/**
* Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
* this observer if it does not already exist.
+ * For executing mitigations observers will receive callback on the given executor.
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
* it will resume observing any packages requested from a previous boot.
- * @hide
+ *
+ * @param observer instance of {@link PackageHealthObserver} for observing package failures
+ * and boot loops.
+ * @param executor Executor for the thread on which observers would receive callbacks
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(@NonNull PackageHealthObserver observer,
+ @NonNull @CallbackExecutor Executor executor) {
synchronized (sLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
} else {
internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
new ArrayList<>());
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
syncState("added new observer");
}
@@ -357,40 +366,53 @@ public class PackageWatchdog {
}
/**
- * Starts observing the health of the {@code packages} for {@code observer} and notifies
- * {@code observer} of any package failures within the monitoring duration.
+ * Starts observing the health of the {@code packages} for {@code observer}.
+ * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
+ * this API.
*
* <p>If monitoring a package supporting explicit health check, at the end of the monitoring
* duration if {@link #onHealthCheckPassed} was never called,
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the package failed.
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
+ * package failed.
*
* <p>If {@code observer} is already monitoring a package in {@code packageNames},
* the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default depending on if the package is contained in
- * {@link mPackagesWithExplicitHealthCheckEnabled}.
+ * check state will be reset to a default.
*
- * <p>If {@code packageNames} is empty, this will be a no-op.
+ * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
+ * calling this method.
*
- * <p>If {@code durationMs} is less than 1, a default monitoring duration
- * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
- * @hide
+ * @param packageNames The list of packages to check. If this is empty, the call will be a
+ * no-op.
+ *
+ * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
+ * less than 1, a default monitoring duration 2 days will be used.
+ *
+ * @throws IllegalStateException if the observer was not previously registered
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- long durationMs) {
+ public void startExplicitHealthCheck(@NonNull PackageHealthObserver observer,
+ @NonNull List<String> packageNames, long timeoutMs) {
+ synchronized (sLock) {
+ if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
+ Slog.wtf(TAG, "No observer found, need to register the observer: "
+ + observer.getUniqueIdentifier());
+ throw new IllegalStateException("Observer not registered");
+ }
+ }
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
return;
}
- if (durationMs < 1) {
- Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
+ if (timeoutMs < 1) {
+ Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
+ observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
- durationMs = DEFAULT_OBSERVING_DURATION_MS;
+ timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
}
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
// Health checks not available yet so health check state will start INACTIVE
- MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
+ MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
if (pkg != null) {
packages.add(pkg);
} else {
@@ -423,9 +445,6 @@ public class PackageWatchdog {
}
}
- // Register observer in case not already registered
- registerHealthObserver(observer);
-
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
syncState("updated observers");
@@ -437,9 +456,8 @@ public class PackageWatchdog {
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
* even from a previous boot.
- * @hide
*/
- public void unregisterHealthObserver(PackageHealthObserver observer) {
+ public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
synchronized (sLock) {
mAllObservers.remove(observer.getUniqueIdentifier());
@@ -485,7 +503,7 @@ public class PackageWatchdog {
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
VersionedPackage versionedPackage = packages.get(pIndex);
// Observer that will receive failure for versionedPackage
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
MonitoredPackage currentMonitoredPackage = null;
@@ -506,7 +524,7 @@ public class PackageWatchdog {
versionedPackage, failureReason, mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
currentMonitoredPackage = p;
}
@@ -515,18 +533,23 @@ public class PackageWatchdog {
// Execute action with least user impact
if (currentObserverToNotify != null) {
- int mitigationCount = 1;
+ int mitigationCount;
if (currentMonitoredPackage != null) {
currentMonitoredPackage.noteMitigationCallLocked();
mitigationCount =
currentMonitoredPackage.getMitigationCountLocked();
+ } else {
+ mitigationCount = 1;
}
if (Flags.recoverabilityDetection()) {
maybeExecute(currentObserverToNotify, versionedPackage,
failureReason, currentObserverImpact, mitigationCount);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(
- versionedPackage, failureReason, mitigationCount);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(
+ versionedPackage, failureReason, mitigationCount));
}
}
}
@@ -539,10 +562,11 @@ public class PackageWatchdog {
* For native crashes or explicit health check failures, call directly into each observer to
* mitigate the error without going through failure threshold logic.
*/
+ @GuardedBy("sLock")
private void handleFailureImmediately(List<VersionedPackage> packages,
@FailureReasons int failureReason) {
VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (ObserverInternal observer: mAllObservers.values()) {
PackageHealthObserver registeredObserver = observer.registeredObserver;
@@ -551,7 +575,7 @@ public class PackageWatchdog {
failingPackage, failureReason, 1);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
@@ -561,23 +585,30 @@ public class PackageWatchdog {
maybeExecute(currentObserverToNotify, failingPackage, failureReason,
currentObserverImpact, /*mitigationCount=*/ 1);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(failingPackage,
- failureReason, 1);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
+ failureReason, 1));
+
}
}
}
- private void maybeExecute(PackageHealthObserver currentObserverToNotify,
+ private void maybeExecute(ObserverInternal currentObserverToNotify,
VersionedPackage versionedPackage,
@FailureReasons int failureReason,
int currentObserverImpact,
int mitigationCount) {
if (allowMitigations(currentObserverImpact, versionedPackage)) {
+ PackageHealthObserver registeredObserver;
synchronized (sLock) {
mLastMitigation = mSystemClock.uptimeMillis();
+ registeredObserver = currentObserverToNotify.registeredObserver;
}
- currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
- mitigationCount);
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
+ failureReason, mitigationCount));
}
}
@@ -613,8 +644,7 @@ public class PackageWatchdog {
mBootThreshold.reset();
}
int mitigationCount = mBootThreshold.getMitigationCount() + 1;
- PackageHealthObserver currentObserverToNotify = null;
- ObserverInternal currentObserverInternal = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
@@ -626,25 +656,31 @@ public class PackageWatchdog {
: registeredObserver.onBootLoop(mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverInternal = observer;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
}
+
if (currentObserverToNotify != null) {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
if (Flags.recoverabilityDetection()) {
int currentObserverMitigationCount =
- currentObserverInternal.getBootMitigationCount() + 1;
- currentObserverInternal.setBootMitigationCount(
+ currentObserverToNotify.getBootMitigationCount() + 1;
+ currentObserverToNotify.setBootMitigationCount(
currentObserverMitigationCount);
saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.onExecuteBootLoopMitigation(
- currentObserverMitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ currentObserverMitigationCount));
} else {
mBootThreshold.setMitigationCount(mitigationCount);
mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.onExecuteBootLoopMitigation(mitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ mitigationCount));
+
}
}
}
@@ -879,7 +915,7 @@ public class PackageWatchdog {
* passed to observers in these API.
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
@@ -1136,8 +1172,10 @@ public class PackageWatchdog {
if (versionedPkg != null) {
Slog.i(TAG,
"Explicit health check failed for package " + versionedPkg);
- registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ observer.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ 1));
}
}
}
@@ -1395,6 +1433,7 @@ public class PackageWatchdog {
@Nullable
@GuardedBy("sLock")
public PackageHealthObserver registeredObserver;
+ public Executor observerExecutor;
private int mMitigationCount;
ObserverInternal(String name, List<MonitoredPackage> packages) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index 992f581a8a70..bad6ab7c1dd4 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -160,7 +160,7 @@ public class RescueParty {
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), context.getMainExecutor());
}
private static boolean isDisabled() {
@@ -313,7 +313,7 @@ public class RescueParty {
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 311def80f248..c80a1a4ea187 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -111,7 +111,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this,
+ context.getMainExecutor());
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -273,16 +274,6 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
Preconditions.checkState(mHandler.getLooper().isCurrentThread());
}
- /**
- * Start observing health of {@code packages} for {@code durationMs}.
- * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
- */
- @AnyThread
- @NonNull
- public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
- }
-
@AnyThread
@NonNull
public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 88fe36cda395..4fea9372971d 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -87,6 +87,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -362,7 +363,7 @@ public class PackageWatchdog {
* it will resume observing any packages requested from a previous boot.
* @hide
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(PackageHealthObserver observer, Executor ignoredExecutor) {
synchronized (mLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
@@ -396,7 +397,7 @@ public class PackageWatchdog {
* {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
* @hide
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
+ public void startExplicitHealthCheck(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
@@ -445,7 +446,7 @@ public class PackageWatchdog {
}
// Register observer in case not already registered
- registerHealthObserver(observer);
+ registerHealthObserver(observer, null);
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
@@ -861,7 +862,7 @@ public class PackageWatchdog {
* otherwise
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index f757236613d1..2bb72fb43dff 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -166,7 +166,7 @@ public class RescueParty {
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), null);
}
private static boolean isDisabled() {
@@ -387,7 +387,7 @@ public class RescueParty {
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7445534e95bf..0692cdbc5e40 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -110,7 +110,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this, null);
mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
@@ -284,7 +284,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
@AnyThread
@NonNull
public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+ PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(this, packages, durationMs);
}
@AnyThread
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index 8617c6a28f43..44dd306372f3 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -83,7 +83,7 @@
<string name="snackbar_action" msgid="37373514216505085">"Voir les options"</string>
<string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
<string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
- <string name="button_label_view_more" msgid="3429098227286495651">"Afficher plus"</string>
+ <string name="button_label_view_more" msgid="3429098227286495651">"Voir plus"</string>
<string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
<string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
<string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Appuyer pour déverrouiller"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index 630a08a7e903..799712710196 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -22,7 +22,7 @@
<string name="string_continue" msgid="1346732695941131882">"Շարունակել"</string>
<string name="string_more_options" msgid="2763852250269945472">"Պահել մեկ այլ եղանակ"</string>
<string name="string_learn_more" msgid="4541600451688392447">"Իմանալ ավելին"</string>
- <string name="content_description_show_password" msgid="3283502010388521607">"Ցուցադրել գաղտնաբառը"</string>
+ <string name="content_description_show_password" msgid="3283502010388521607">"Ցույց տալ գաղտնաբառը"</string>
<string name="content_description_hide_password" msgid="6841375971631767996">"Թաքցնել գաղտնաբառը"</string>
<string name="passkey_creation_intro_title" msgid="4251037543787718844">"Մուտքի բանալիների հետ ավելի ապահով է"</string>
<string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Մուտքի բանալիների շնորհիվ դուք բարդ գաղտնաբառեր ստեղծելու կամ հիշելու անհրաժեշտություն չեք ունենա"</string>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 6a4bb216b495..a3b06e8c71fc 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -16,8 +16,10 @@
package com.android.localtransport;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManagerMonitor;
@@ -52,6 +54,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
/**
* Backup transport for stashing stuff into a known location on disk, and
@@ -939,4 +942,15 @@ public class LocalTransport extends BackupTransport {
}
}
}
+
+ @NonNull
+ @Override
+ public List<String> getPackagesThatShouldNotUseRestrictedMode(
+ @NonNull List<String> packageNames,
+ @BackupAnnotations.OperationType int operationType) {
+ if (DEBUG) {
+ Log.d(TAG, "No restricted mode packages: " + mParameters.noRestrictedModePackages());
+ }
+ return mParameters.noRestrictedModePackages();
+ }
}
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index aaa18bf755bc..c980913f80c6 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -16,26 +16,33 @@
package com.android.localtransport;
-import android.util.KeyValueSettingObserver;
import android.content.ContentResolver;
import android.os.Handler;
import android.provider.Settings;
import android.util.KeyValueListParser;
+import android.util.KeyValueSettingObserver;
+
+import java.util.Arrays;
+import java.util.List;
public class LocalTransportParameters extends KeyValueSettingObserver {
- private static final String TAG = "LocalTransportParams";
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
private static final String KEY_IS_ENCRYPTED = "is_encrypted";
private static final String KEY_LOG_AGENT_RESULTS = "log_agent_results";
+ // This needs to be a list of package names separated by semicolons. For example:
+ // "com.package1;com.package2;com.package3". We can't use commas because the base class uses
+ // commas to split Key/Value pairs.
+ private static final String KEY_NO_RESTRICTED_MODE_PACKAGES = "no_restricted_mode_packages";
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
private boolean mIsDeviceTransfer;
private boolean mIsEncrypted;
private boolean mLogAgentResults;
+ private String mNoRestrictedModePackages;
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -61,6 +68,13 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
return mLogAgentResults;
}
+ List<String> noRestrictedModePackages() {
+ if (mNoRestrictedModePackages == null) {
+ return List.of();
+ }
+ return Arrays.stream(mNoRestrictedModePackages.split(";")).toList();
+ }
+
public String getSettingValue(ContentResolver resolver) {
return Settings.Secure.getString(resolver, SETTING);
}
@@ -71,5 +85,6 @@ public class LocalTransportParameters extends KeyValueSettingObserver {
mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
mLogAgentResults = parser.getBoolean(KEY_LOG_AGENT_RESULTS, /* def */ false);
+ mNoRestrictedModePackages = parser.getString(KEY_NO_RESTRICTED_MODE_PACKAGES, /* def */ "");
}
}
diff --git a/packages/NeuralNetworks/framework/Android.bp b/packages/NeuralNetworks/framework/Android.bp
new file mode 100644
index 000000000000..6f45daae0802
--- /dev/null
+++ b/packages/NeuralNetworks/framework/Android.bp
@@ -0,0 +1,30 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "framework-ondeviceintelligence-sources",
+ srcs: [
+ "java/**/*.aidl",
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: [
+ "//frameworks/base:__subpackages__",
+ "//packages/modules/NeuralNetworks:__subpackages__",
+ ],
+}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
index 30c6e1924942..95fb2888a3e9 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -23,8 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.PersistableBundle;
-
-import androidx.annotation.IntDef;
+import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,14 +52,14 @@ public interface DownloadCallback {
/**
* Sent when feature download has been initiated already, hence no need to request download
- * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if
+ * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check if
* download has been completed.
*/
int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3;
/**
* Sent when feature download did not start due to errors (e.g. remote exception of features not
- * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check
+ * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check
* if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}.
*/
int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4;
@@ -72,7 +71,7 @@ public interface DownloadCallback {
DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE,
DOWNLOAD_FAILURE_STATUS_DOWNLOADING,
DOWNLOAD_FAILURE_STATUS_UNAVAILABLE
- }, open = true)
+ })
@Retention(RetentionPolicy.SOURCE)
@interface DownloadFailureStatus {
}
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
index 18494d754674..47cfb4a60dc4 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
@@ -19,4 +19,4 @@ package android.app.ondeviceintelligence;
/**
* @hide
*/
-parcelable Feature;
+@JavaOnlyStableParcelable parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
index bcc56073e51c..88f4de2989e4 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
@@ -26,6 +26,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import java.util.Objects;
+
/**
* Represents a typical feature associated with on-device intelligence.
*
@@ -56,9 +58,8 @@ public final class Feature implements Parcelable {
this.mModelName = modelName;
this.mType = type;
this.mVariant = variant;
- this.mFeatureParams = featureParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureParams);
+ this.mFeatureParams = Objects.requireNonNull(featureParams,
+ "featureParams should be non-null.");
}
/** Returns the unique and immutable identifier of this feature. */
@@ -167,8 +168,6 @@ public final class Feature implements Parcelable {
this.mType = type;
this.mVariant = variant;
this.mFeatureParams = featureParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureParams);
}
public static final @NonNull Parcelable.Creator<Feature> CREATOR
@@ -200,6 +199,7 @@ public final class Feature implements Parcelable {
/**
* Provides a builder instance to create a feature for given id.
+ *
* @param id the unique identifier for the feature.
*/
public Builder(int id) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
index 0589bf8bacb9..c5b3532796cd 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -19,4 +19,4 @@ package android.app.ondeviceintelligence;
/**
* @hide
*/
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable FeatureDetails;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
index 44930f2c34f4..063cfb8c321e 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -19,18 +19,18 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import androidx.annotation.IntDef;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.text.MessageFormat;
+import java.util.Objects;
/**
* Represents a status of a requested {@link Feature}.
@@ -69,7 +69,7 @@ public final class FeatureDetails implements Parcelable {
FEATURE_STATUS_DOWNLOADING,
FEATURE_STATUS_AVAILABLE,
FEATURE_STATUS_SERVICE_UNAVAILABLE
- }, open = true)
+ })
@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
@@ -79,18 +79,12 @@ public final class FeatureDetails implements Parcelable {
@Status int featureStatus,
@NonNull PersistableBundle featureDetailParams) {
this.mFeatureStatus = featureStatus;
- com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mFeatureStatus);
- this.mFeatureDetailParams = featureDetailParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureDetailParams);
+ this.mFeatureDetailParams = Objects.requireNonNull(featureDetailParams);
}
public FeatureDetails(
@Status int featureStatus) {
this.mFeatureStatus = featureStatus;
- com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = new PersistableBundle();
}
@@ -155,11 +149,7 @@ public final class FeatureDetails implements Parcelable {
PersistableBundle.CREATOR);
this.mFeatureStatus = status;
- com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = persistableBundle;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureDetailParams);
}
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
new file mode 100644
index 000000000000..1fe201f8f1f8
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+oneway interface ICancellationSignal {
+ void cancel();
+}
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index 2d7ea1a7b016..2d7ea1a7b016 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
index 2e056926e400..2e056926e400 100644
--- a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
index 8688028743d7..8688028743d7 100644
--- a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
index 7e5eb57bbc4a..7e5eb57bbc4a 100644
--- a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 1977a3923578..fac5ec6064f8 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -16,8 +16,8 @@
package android.app.ondeviceintelligence;
- import com.android.internal.infra.AndroidFuture;
- import android.os.ICancellationSignal;
+ import com.android.modules.utils.AndroidFuture;
+ import android.app.ondeviceintelligence.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
index 03946eebd40b..03946eebd40b 100644
--- a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
new file mode 100644
index 000000000000..6f07693dd39c
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl
@@ -0,0 +1,24 @@
+/*
+* Copyright 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.app.ondeviceintelligence;
+
+import android.os.Bundle;
+
+/* @hide */
+oneway interface IRemoteCallback {
+ void sendResult(in Bundle data);
+}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 270b600e2de5..270b600e2de5 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 3e902405f3e0..3e902405f3e0 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
index 958bef0a93e0..958bef0a93e0 100644
--- a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
index 6d70fc4577a2..6f6325408979 100644
--- a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -19,4 +19,4 @@ package android.app.ondeviceintelligence;
/**
* @hide
*/
-parcelable InferenceInfo;
+@JavaOnlyStableParcelable parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..cae8db27a435 100644
--- a/core/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
index 03ff563a88c0..2881c9d217dc 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -20,13 +20,14 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.PersistableBundle;
-import androidx.annotation.IntDef;
-
import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
@@ -124,8 +125,9 @@ public class OnDeviceIntelligenceException extends Exception {
PROCESSING_ERROR_SERVICE_UNAVAILABLE,
ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
- }, open = true)
+ })
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @Retention(RetentionPolicy.SOURCE)
@interface OnDeviceIntelligenceError {
}
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
new file mode 100644
index 000000000000..7d35dd7f2237
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for OnDeviceIntelligence service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceFrameworkInitializer {
+ private OnDeviceIntelligenceFrameworkInitializer() {
+ }
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers
+ * OnDeviceIntelligence service to {@link Context}, so that {@link Context#getSystemService} can
+ * return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides {@link
+ * SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(Context.ON_DEVICE_INTELLIGENCE_SERVICE,
+ OnDeviceIntelligenceManager.class,
+ (context, serviceBinder) -> {
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(serviceBinder);
+ return new OnDeviceIntelligenceManager(context, manager);
+ });
+ }
+} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 91651e317b18..dc0665a5cea7 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -23,18 +23,18 @@ import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.ondeviceintelligence.utils.BinderUtils;
import android.content.Context;
import android.graphics.Bitmap;
-import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
-import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -42,9 +42,7 @@ import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;
-import androidx.annotation.IntDef;
-
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -80,10 +78,39 @@ public final class OnDeviceIntelligenceManager {
public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
"AugmentRequestContentBundleKey";
+ /**
+ * Timeout to be used for unbinding to the configured remote {@link
+ * android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no requests in
+ * the queue. A value of -1 represents to never unbind.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS =
+ "on_device_intelligence_unbind_timeout_ms";
+
+ /**
+ * Timeout that represents maximum idle time before which a callback should be populated.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS =
+ "on_device_intelligence_idle_timeout_ms";
+
+ /**
+ * Timeout to be used for unbinding to the configured remote {@link
+ * android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there are no
+ * requests in the queue. A value of -1 represents to never unbind.
+ *
+ * @hide
+ */
+ public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS =
+ "on_device_inference_unbind_timeout_ms";
+
private static final String TAG = "OnDeviceIntelligence";
private final Context mContext;
private final IOnDeviceIntelligenceManager mService;
+
/**
* @hide
*/
@@ -105,11 +132,11 @@ public final class OnDeviceIntelligenceManager {
try {
RemoteCallback callback = new RemoteCallback(result -> {
if (result == null) {
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> callbackExecutor.execute(() -> versionConsumer.accept(0)));
}
long version = result.getLong(API_VERSION_BUNDLE_KEY);
- Binder.withCleanCallingIdentity(
+ BinderUtils.withCleanCallingIdentity(
() -> callbackExecutor.execute(() -> versionConsumer.accept(version)));
});
mService.getVersion(callback);
@@ -151,14 +178,14 @@ public final class OnDeviceIntelligenceManager {
new IFeatureCallback.Stub() {
@Override
public void onSuccess(Feature result) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onResult(result)));
}
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
@@ -185,14 +212,14 @@ public final class OnDeviceIntelligenceManager {
new IListFeaturesCallback.Stub() {
@Override
public void onSuccess(List<Feature> result) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onResult(result)));
}
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
@@ -223,14 +250,14 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onSuccess(FeatureDetails result) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onResult(result)));
}
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
@@ -268,27 +295,27 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onDownloadStarted(long bytesToDownload) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> callback.onDownloadStarted(bytesToDownload)));
}
@Override
public void onDownloadProgress(long bytesDownloaded) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> callback.onDownloadProgress(bytesDownloaded)));
}
@Override
public void onDownloadFailed(int failureStatus, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> callback.onDownloadFailed(failureStatus, errorMessage,
errorParams)));
}
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -325,14 +352,14 @@ public final class OnDeviceIntelligenceManager {
ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
public void onSuccess(TokenInfo tokenInfo) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onResult(tokenInfo)));
}
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
@@ -377,7 +404,7 @@ public final class OnDeviceIntelligenceManager {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
public void onSuccess(@InferenceParams Bundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(() -> processingCallback.onResult(result));
});
}
@@ -385,7 +412,7 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> processingCallback.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
@@ -394,7 +421,7 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
@@ -447,7 +474,7 @@ public final class OnDeviceIntelligenceManager {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(@InferenceParams Bundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onPartialResult(result));
});
@@ -455,7 +482,7 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onSuccess(@InferenceParams Bundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onResult(result));
});
@@ -464,7 +491,7 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onError(
new OnDeviceIntelligenceException(
@@ -476,7 +503,7 @@ public final class OnDeviceIntelligenceManager {
@Override
public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
@NonNull RemoteCallback contentCallback) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> streamingProcessingCallback.onDataAugmentRequest(content,
contentResponse -> {
Bundle bundle = new Bundle();
@@ -537,7 +564,7 @@ public final class OnDeviceIntelligenceManager {
REQUEST_TYPE_INFERENCE,
REQUEST_TYPE_PREPARE,
REQUEST_TYPE_EMBEDDINGS
- }, open = true)
+ })
@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
@@ -614,8 +641,17 @@ public final class OnDeviceIntelligenceManager {
if (error != null || cancellationTransport == null) {
Log.e(TAG, "Unable to receive the remote cancellation signal.", error);
} else {
- cancellationSignal.setRemote(
- ICancellationSignal.Stub.asInterface(cancellationTransport));
+ ICancellationSignal remoteCancellationSignal =
+ ICancellationSignal.Stub.asInterface(cancellationTransport);
+ cancellationSignal.setOnCancelListener(
+ () -> {
+ try {
+ remoteCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to propagate cancellation signal.",
+ e);
+ }
+ });
}
}, callbackExecutor);
return cancellationFuture;
@@ -638,6 +674,4 @@ public final class OnDeviceIntelligenceManager {
}, executor);
return processingSignalFuture;
}
-
-
-}
+} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java
index e50d6b1fa97a..e50d6b1fa97a 100644
--- a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java
index 733f4fad96f4..733f4fad96f4 100644
--- a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java
diff --git a/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index 7ee2af7376ed..7ee2af7376ed 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
index 2c19c1eb246c..599b337fd20f 100644
--- a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -19,4 +19,4 @@ package android.app.ondeviceintelligence;
/**
* @hide
*/
-parcelable TokenInfo;
+@JavaOnlyStableParcelable parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java
index 035cc4b365b5..035cc4b365b5 100644
--- a/core/java/android/app/ondeviceintelligence/TokenInfo.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java
diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java
new file mode 100644
index 000000000000..2916f030e3d0
--- /dev/null
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.app.ondeviceintelligence.utils;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+
+import java.util.function.Supplier;
+
+/**
+ * Collection of utilities for {@link Binder} and related classes.
+ * @hide
+ */
+public class BinderUtils {
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+ *
+ * Any exception thrown by the given action will be caught and rethrown after the call to
+ * {@link Binder#restoreCallingIdentity}
+ *
+ * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+ * since it is not public.
+ *
+ * @hide
+ */
+ public static final <T extends Exception> void withCleanCallingIdentity(
+ @NonNull ThrowingRunnable<T> action) throws T {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ action.run();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Like a Runnable, but declared to throw an exception.
+ *
+ * @param <T> The exception class which is declared to be thrown.
+ */
+ @FunctionalInterface
+ public interface ThrowingRunnable<T extends Exception> {
+ /** @see java.lang.Runnable */
+ void run() throws T;
+ }
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the
+ * result.
+ *
+ * <p>Any exception thrown by the given action will be caught and rethrown after
+ * the call to {@link Binder#restoreCallingIdentity}.
+ *
+ * Note that this is copied from Binder#withCleanCallingIdentity with minor changes
+ * since it is not public.
+ *
+ * @hide
+ */
+ public static final <T, E extends Exception> T withCleanCallingIdentity(
+ @NonNull ThrowingSupplier<T, E> action) throws E {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return action.get();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * An equivalent of {@link Supplier}
+ *
+ * @param <T> The class which is declared to be returned.
+ * @param <E> The exception class which is declared to be thrown.
+ */
+ @FunctionalInterface
+ public interface ThrowingSupplier<T, E extends Exception> {
+ /** @see java.util.function.Supplier */
+ T get() throws E;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index 45c43502d6de..cba18c1ef36d 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -18,14 +18,14 @@ package android.service.ondeviceintelligence;
import android.os.PersistableBundle;
import android.os.ParcelFileDescriptor;
-import android.os.ICancellationSignal;
+import android.app.ondeviceintelligence.ICancellationSignal;
import android.os.RemoteCallback;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import android.service.ondeviceintelligence.IRemoteProcessingService;
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 1af3b0f374f1..504fdd9b17f9 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -21,11 +21,11 @@ import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.Feature;
-import android.os.IRemoteCallback;
-import android.os.ICancellationSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
+import android.app.ondeviceintelligence.ICancellationSignal;
import android.os.PersistableBundle;
import android.os.Bundle;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import android.service.ondeviceintelligence.IRemoteStorageService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
diff --git a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
index 7ead8690abb4..7ead8690abb4 100644
--- a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
index 32a8a6a70406..32a8a6a70406 100644
--- a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
index a6f49e17d80e..253df890b198 100644
--- a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
@@ -20,7 +20,7 @@ import android.app.ondeviceintelligence.Feature;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
/**
* Interface for a concrete implementation to provide access to storage read access
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index d82fe1ca885c..6907e2bdf2b3 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -18,8 +18,6 @@ package android.service.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
import android.annotation.CallSuper;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -27,11 +25,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Service;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.ICancellationSignal;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
@@ -39,14 +37,14 @@ import android.app.ondeviceintelligence.IListFeaturesCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
+import android.app.ondeviceintelligence.utils.BinderUtils;
import android.content.Intent;
-import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
-import android.os.ICancellationSignal;
import android.os.Looper;
+import android.os.Message;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -55,10 +53,11 @@ import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -92,6 +91,18 @@ import java.util.function.LongConsumer;
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public abstract class OnDeviceIntelligenceService extends Service {
+ private static final int MSG_ON_READY = 1;
+ private static final int MSG_GET_VERSION = 2;
+ private static final int MSG_LIST_FEATURES = 3;
+ private static final int MSG_GET_FEATURE = 4;
+ private static final int MSG_GET_FEATURE_DETAILS = 5;
+ private static final int MSG_DOWNLOAD_FEATURE = 6;
+ private static final int MSG_GET_READ_ONLY_FILE_DESCRIPTOR = 7;
+ private static final int MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP = 8;
+ private static final int MSG_REGISTER_REMOTE_SERVICES = 9;
+ private static final int MSG_INFERENCE_SERVICE_CONNECTED = 10;
+ private static final int MSG_INFERENCE_SERVICE_DISCONNECTED = 11;
+
private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
private volatile IRemoteProcessingService mRemoteProcessingService;
@@ -101,19 +112,71 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void onCreate() {
super.onCreate();
- mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+ mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case MSG_ON_READY:
+ OnDeviceIntelligenceService.this.onReady();
+ break;
+ case MSG_GET_VERSION:
+ OnDeviceIntelligenceService.this.onGetVersion(
+ (LongConsumer) msg.obj);
+ break;
+ case MSG_LIST_FEATURES:
+ OnDeviceIntelligenceService.this.onListFeatures(
+ msg.arg1,
+ (OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException>) msg.obj);
+ break;
+ case MSG_GET_FEATURE:
+ GetFeatureParams params = (GetFeatureParams) msg.obj;
+ OnDeviceIntelligenceService.this.onGetFeature(
+ msg.arg1,
+ msg.arg2,
+ params.callback);
+ break;
+ case MSG_GET_FEATURE_DETAILS:
+ FeatureDetailsParams detailsParams = (FeatureDetailsParams) msg.obj;
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(
+ msg.arg1,
+ detailsParams.feature,
+ detailsParams.callback);
+ break;
+ case MSG_DOWNLOAD_FEATURE:
+ DownloadParams downloadParams = (DownloadParams) msg.obj;
+ OnDeviceIntelligenceService.this.onDownloadFeature(
+ msg.arg1,
+ downloadParams.feature,
+ downloadParams.cancellationSignal,
+ downloadParams.callback);
+ break;
+ case MSG_GET_READ_ONLY_FILE_DESCRIPTOR:
+ FileDescriptorParams fdParams = (FileDescriptorParams) msg.obj;
+ OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(
+ fdParams.fileName,
+ fdParams.future);
+ break;
+ case MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP:
+ FeatureFileDescriptorParams ffdParams =
+ (FeatureFileDescriptorParams) msg.obj;
+ OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap(
+ ffdParams.feature,
+ ffdParams.consumer);
+ break;
+ case MSG_REGISTER_REMOTE_SERVICES:
+ mRemoteProcessingService = (IRemoteProcessingService) msg.obj;
+ break;
+ case MSG_INFERENCE_SERVICE_CONNECTED:
+ OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+ break;
+ case MSG_INFERENCE_SERVICE_DISCONNECTED:
+ OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+ break;
+ }
+ }
+ };
}
- /**
- * The {@link Intent} that must be declared as handled by the service. To be supported, the
- * service must also require the
- * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
- * permission so that other applications can not abuse it.
- */
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE =
- "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
-
/**
* @hide
@@ -126,45 +189,37 @@ public abstract class OnDeviceIntelligenceService extends Service {
/** {@inheritDoc} */
@Override
public void ready() {
- mHandler.executeOrSendMessage(
- obtainMessage(OnDeviceIntelligenceService::onReady,
- OnDeviceIntelligenceService.this));
+ mHandler.sendEmptyMessage(MSG_ON_READY);
}
@Override
public void getVersion(RemoteCallback remoteCallback) {
Objects.requireNonNull(remoteCallback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onGetVersion,
- OnDeviceIntelligenceService.this, l -> {
- Bundle b = new Bundle();
- b.putLong(
- OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY,
- l);
- remoteCallback.sendResult(b);
- }));
+ Message msg = Message.obtain(mHandler, MSG_GET_VERSION,
+ (LongConsumer) (l -> {
+ Bundle b = new Bundle();
+ b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l);
+ remoteCallback.sendResult(b);
+ }));
+ mHandler.sendMessage(msg);
}
@Override
public void listFeatures(int callerUid,
IListFeaturesCallback listFeaturesCallback) {
Objects.requireNonNull(listFeaturesCallback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onListFeatures,
- OnDeviceIntelligenceService.this, callerUid,
- wrapListFeaturesCallback(listFeaturesCallback)));
+ Message msg = Message.obtain(mHandler, MSG_LIST_FEATURES,
+ callerUid, 0, wrapListFeaturesCallback(listFeaturesCallback));
+ mHandler.sendMessage(msg);
}
@Override
public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
Objects.requireNonNull(featureCallback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onGetFeature,
- OnDeviceIntelligenceService.this, callerUid,
- id, wrapFeatureCallback(featureCallback)));
+ Message msg = Message.obtain(mHandler, MSG_GET_FEATURE,
+ callerUid, id,
+ new GetFeatureParams(wrapFeatureCallback(featureCallback)));
+ mHandler.sendMessage(msg);
}
@@ -173,11 +228,11 @@ public abstract class OnDeviceIntelligenceService extends Service {
IFeatureDetailsCallback featureDetailsCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(featureDetailsCallback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onGetFeatureDetails,
- OnDeviceIntelligenceService.this, callerUid,
- feature, wrapFeatureDetailsCallback(featureDetailsCallback)));
+ Message msg = Message.obtain(mHandler, MSG_GET_FEATURE_DETAILS,
+ new FeatureDetailsParams(feature,
+ wrapFeatureDetailsCallback(featureDetailsCallback)));
+ msg.arg1 = callerUid;
+ mHandler.sendMessage(msg);
}
@Override
@@ -186,18 +241,24 @@ public abstract class OnDeviceIntelligenceService extends Service {
IDownloadCallback downloadCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(downloadCallback);
- ICancellationSignal transport = null;
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
if (cancellationSignalFuture != null) {
- transport = CancellationSignal.createTransport();
+ ICancellationSignal transport = new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() {
+ cancellationSignal.cancel();
+ }
+ };
cancellationSignalFuture.complete(transport);
}
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onDownloadFeature,
- OnDeviceIntelligenceService.this, callerUid,
- feature,
- CancellationSignal.fromTransport(transport),
+
+ Message msg = Message.obtain(mHandler, MSG_DOWNLOAD_FEATURE,
+ new DownloadParams(feature,
+ cancellationSignalFuture != null ? cancellationSignal : null,
wrapDownloadCallback(downloadCallback)));
+ msg.arg1 = callerUid;
+ mHandler.sendMessage(msg);
}
@Override
@@ -205,11 +266,9 @@ public abstract class OnDeviceIntelligenceService extends Service {
AndroidFuture<ParcelFileDescriptor> future) {
Objects.requireNonNull(fileName);
Objects.requireNonNull(future);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor,
- OnDeviceIntelligenceService.this, fileName,
- future));
+ Message msg = Message.obtain(mHandler, MSG_GET_READ_ONLY_FILE_DESCRIPTOR,
+ new FileDescriptorParams(fileName, future));
+ mHandler.sendMessage(msg);
}
@Override
@@ -217,16 +276,15 @@ public abstract class OnDeviceIntelligenceService extends Service {
Feature feature, RemoteCallback remoteCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(remoteCallback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap,
- OnDeviceIntelligenceService.this, feature,
- parcelFileDescriptorMap -> {
- Bundle bundle = new Bundle();
- parcelFileDescriptorMap.forEach(bundle::putParcelable);
- remoteCallback.sendResult(bundle);
- tryClosePfds(parcelFileDescriptorMap.values());
- }));
+ Message msg = Message.obtain(mHandler,
+ MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP,
+ new FeatureFileDescriptorParams(feature, parcelFileDescriptorMap -> {
+ Bundle bundle = new Bundle();
+ parcelFileDescriptorMap.forEach(bundle::putParcelable);
+ remoteCallback.sendResult(bundle);
+ tryClosePfds(parcelFileDescriptorMap.values());
+ }));
+ mHandler.sendMessage(msg);
}
@Override
@@ -237,18 +295,12 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void notifyInferenceServiceConnected() {
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onInferenceServiceConnected,
- OnDeviceIntelligenceService.this));
+ mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_CONNECTED);
}
@Override
public void notifyInferenceServiceDisconnected() {
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceIntelligenceService::onInferenceServiceDisconnected,
- OnDeviceIntelligenceService.this));
+ mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_DISCONNECTED);
}
};
}
@@ -257,13 +309,77 @@ public abstract class OnDeviceIntelligenceService extends Service {
}
/**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+
+ // Parameter holder classes
+ private static class GetFeatureParams {
+ final OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback;
+
+ GetFeatureParams(OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback) {
+ this.callback = callback;
+ }
+ }
+
+ private static class FeatureDetailsParams {
+ final Feature feature;
+ final OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback;
+
+ FeatureDetailsParams(Feature feature,
+ OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback) {
+ this.feature = feature;
+ this.callback = callback;
+ }
+ }
+
+ private static class DownloadParams {
+ final Feature feature;
+ final CancellationSignal cancellationSignal;
+ final DownloadCallback callback;
+
+ DownloadParams(Feature feature, CancellationSignal cancellationSignal,
+ DownloadCallback callback) {
+ this.feature = feature;
+ this.cancellationSignal = cancellationSignal;
+ this.callback = callback;
+ }
+ }
+
+ private static class FileDescriptorParams {
+ final String fileName;
+ final AndroidFuture<ParcelFileDescriptor> future;
+
+ FileDescriptorParams(String fileName, AndroidFuture<ParcelFileDescriptor> future) {
+ this.fileName = fileName;
+ this.future = future;
+ }
+ }
+
+ private static class FeatureFileDescriptorParams {
+ final Feature feature;
+ final Consumer<Map<String, ParcelFileDescriptor>> consumer;
+
+ FeatureFileDescriptorParams(Feature feature,
+ Consumer<Map<String, ParcelFileDescriptor>> consumer) {
+ this.feature = feature;
+ this.consumer = consumer;
+ }
+ }
+
+ /**
* Using this signal to assertively a signal each time service binds successfully, used only in
* tests to get a signal that service instance is ready. This is needed because we cannot rely
* on {@link #onCreate} or {@link #onBind} to be invoke on each binding.
*
* @hide
*/
- @TestApi
+ @SystemApi
public void onReady() {
}
@@ -306,7 +422,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
new IProcessingUpdateStatusCallback.Stub() {
@Override
public void onSuccess(PersistableBundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> statusReceiver.onResult(result));
});
@@ -314,7 +430,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
@Override
public void onFailure(int errorCode, String errorMessage) {
- Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage))));
@@ -459,7 +575,7 @@ public abstract class OnDeviceIntelligenceService extends Service {
private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
@NonNull AndroidFuture<ParcelFileDescriptor> future) {
Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
Slog.v(TAG,
"onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
File f = new File(getBaseContext().getFilesDir(), fileName);
@@ -476,7 +592,11 @@ public abstract class OnDeviceIntelligenceService extends Service {
} finally {
future.complete(pfd);
if (pfd != null) {
- pfd.close();
+ try {
+ pfd.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Error closing FD", e);
+ }
}
}
});
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 3181556eded7..315dbaf919e5 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -19,10 +19,8 @@ package android.service.ondeviceintelligence;
import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.CallbackExecutor;
import android.annotation.CallSuper;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,7 +29,9 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.ICancellationSignal;
import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
@@ -48,11 +48,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
import android.os.Looper;
+import android.os.Message;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -61,7 +59,8 @@ import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
+import com.android.modules.utils.HandlerExecutor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -100,6 +99,12 @@ import java.util.function.Consumer;
public abstract class OnDeviceSandboxedInferenceService extends Service {
private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
+ private static final int MSG_TOKEN_INFO_REQUEST = 1;
+ private static final int MSG_PROCESS_REQUEST_STREAMING = 2;
+ private static final int MSG_PROCESS_REQUEST = 3;
+ private static final int MSG_UPDATE_PROCESSING_STATE = 4;
+
+
/**
* @hide
*/
@@ -133,12 +138,12 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
* @hide
*/
public static final String MODEL_LOADED_BROADCAST_INTENT =
- "android.service.ondeviceintelligence.MODEL_LOADED";
+ "android.service.ondeviceintelligence.MODEL_LOADED";
/**
* @hide
*/
public static final String MODEL_UNLOADED_BROADCAST_INTENT =
- "android.service.ondeviceintelligence.MODEL_UNLOADED";
+ "android.service.ondeviceintelligence.MODEL_UNLOADED";
/**
* @hide
@@ -152,12 +157,115 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onCreate() {
super.onCreate();
- mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
+ mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TOKEN_INFO_REQUEST:
+ TokenInfoParams params = (TokenInfoParams) msg.obj;
+ OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(
+ msg.arg1,
+ params.feature,
+ params.request,
+ params.cancellationSignal,
+ params.callback);
+ break;
+ case MSG_PROCESS_REQUEST_STREAMING:
+ StreamingRequestParams streamParams = (StreamingRequestParams) msg.obj;
+ OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(
+ msg.arg1,
+ streamParams.feature,
+ streamParams.request,
+ msg.arg2,
+ streamParams.cancellationSignal,
+ streamParams.processingSignal,
+ streamParams.callback);
+ break;
+ case MSG_PROCESS_REQUEST:
+ RequestParams requestParams = (RequestParams) msg.obj;
+ OnDeviceSandboxedInferenceService.this.onProcessRequest(
+ msg.arg1,
+ requestParams.feature,
+ requestParams.request,
+ msg.arg2,
+ requestParams.cancellationSignal,
+ requestParams.processingSignal,
+ requestParams.callback);
+ break;
+ case MSG_UPDATE_PROCESSING_STATE:
+ UpdateStateParams stateParams = (UpdateStateParams) msg.obj;
+ OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(
+ stateParams.processingState,
+ stateParams.callback);
+ break;
+ }
+ }
+ };
+ }
+
+ // Parameter holder classes
+ private static class TokenInfoParams {
+ final Feature feature;
+ final Bundle request;
+ final CancellationSignal cancellationSignal;
+ final OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback;
+
+ TokenInfoParams(Feature feature, Bundle request, CancellationSignal cancellationSignal,
+ OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback) {
+ this.feature = feature;
+ this.request = request;
+ this.cancellationSignal = cancellationSignal;
+ this.callback = callback;
+ }
+ }
+
+ private static class StreamingRequestParams {
+ final Feature feature;
+ final Bundle request;
+ final CancellationSignal cancellationSignal;
+ final ProcessingSignal processingSignal;
+ final StreamingProcessingCallback callback;
+
+ StreamingRequestParams(Feature feature, Bundle request,
+ CancellationSignal cancellationSignal, ProcessingSignal processingSignal,
+ StreamingProcessingCallback callback) {
+ this.feature = feature;
+ this.request = request;
+ this.cancellationSignal = cancellationSignal;
+ this.processingSignal = processingSignal;
+ this.callback = callback;
+ }
+ }
+
+ private static class RequestParams {
+ final Feature feature;
+ final Bundle request;
+ final CancellationSignal cancellationSignal;
+ final ProcessingSignal processingSignal;
+ final ProcessingCallback callback;
+
+ RequestParams(Feature feature, Bundle request,
+ CancellationSignal cancellationSignal, ProcessingSignal processingSignal,
+ ProcessingCallback callback) {
+ this.feature = feature;
+ this.request = request;
+ this.cancellationSignal = cancellationSignal;
+ this.processingSignal = processingSignal;
+ this.callback = callback;
+ }
+ }
+
+ private static class UpdateStateParams {
+ final Bundle processingState;
+ final OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback;
+
+ UpdateStateParams(Bundle processingState,
+ OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback) {
+ this.processingState = processingState;
+ this.callback = callback;
+ }
}
- /**
- * @hide
- */
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -168,8 +276,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
IRemoteCallback remoteCallback) throws RemoteException {
Objects.requireNonNull(storageService);
mRemoteStorageService = storageService;
- remoteCallback.sendResult(
- Bundle.EMPTY); //to notify caller uid to system-server.
+ remoteCallback.sendResult(Bundle.EMPTY);
}
@Override
@@ -178,34 +285,42 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(tokenInfoCallback);
- ICancellationSignal transport = null;
+ CancellationSignal cancellationSignal = new CancellationSignal();
if (cancellationSignalFuture != null) {
- transport = CancellationSignal.createTransport();
+ ICancellationSignal transport = new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() {
+ cancellationSignal.cancel();
+ }
+ };
cancellationSignalFuture.complete(transport);
}
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceSandboxedInferenceService::onTokenInfoRequest,
- OnDeviceSandboxedInferenceService.this,
- callerUid, feature,
- request,
- CancellationSignal.fromTransport(transport),
+ Message msg = Message.obtain(mHandler, MSG_TOKEN_INFO_REQUEST,
+ callerUid, 0,
+ new TokenInfoParams(feature, request,
+ cancellationSignalFuture != null ? cancellationSignal : null,
wrapTokenInfoCallback(tokenInfoCallback)));
+ mHandler.sendMessage(msg);
}
@Override
- public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
- int requestType,
+ public void processRequestStreaming(int callerUid, Feature feature,
+ Bundle request, int requestType,
AndroidFuture cancellationSignalFuture,
AndroidFuture processingSignalFuture,
IStreamingResponseCallback callback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(callback);
- ICancellationSignal transport = null;
+ CancellationSignal cancellationSignal = new CancellationSignal();
if (cancellationSignalFuture != null) {
- transport = CancellationSignal.createTransport();
+ ICancellationSignal transport = new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() {
+ cancellationSignal.cancel();
+ }
+ };
cancellationSignalFuture.complete(transport);
}
IProcessingSignal processingSignalTransport = null;
@@ -214,30 +329,32 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
processingSignalFuture.complete(processingSignalTransport);
}
-
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceSandboxedInferenceService::onProcessRequestStreaming,
- OnDeviceSandboxedInferenceService.this, callerUid,
- feature,
- request,
- requestType,
- CancellationSignal.fromTransport(transport),
+ Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST_STREAMING,
+ callerUid, requestType,
+ new StreamingRequestParams(feature, request,
+ cancellationSignalFuture != null ? cancellationSignal : null,
ProcessingSignal.fromTransport(processingSignalTransport),
wrapStreamingResponseCallback(callback)));
+ mHandler.sendMessage(msg);
}
@Override
- public void processRequest(int callerUid, Feature feature, Bundle request,
- int requestType,
+ public void processRequest(int callerUid, Feature feature,
+ Bundle request, int requestType,
AndroidFuture cancellationSignalFuture,
AndroidFuture processingSignalFuture,
IResponseCallback callback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(callback);
- ICancellationSignal transport = null;
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
if (cancellationSignalFuture != null) {
- transport = CancellationSignal.createTransport();
+ ICancellationSignal transport = new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() {
+ cancellationSignal.cancel();
+ }
+ };
cancellationSignalFuture.complete(transport);
}
IProcessingSignal processingSignalTransport = null;
@@ -245,14 +362,14 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
processingSignalTransport = ProcessingSignal.createTransport();
processingSignalFuture.complete(processingSignalTransport);
}
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceSandboxedInferenceService::onProcessRequest,
- OnDeviceSandboxedInferenceService.this, callerUid, feature,
- request, requestType,
- CancellationSignal.fromTransport(transport),
+
+ Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST,
+ callerUid, requestType,
+ new RequestParams(feature, request,
+ cancellationSignalFuture != null ? cancellationSignal : null,
ProcessingSignal.fromTransport(processingSignalTransport),
wrapResponseCallback(callback)));
+ mHandler.sendMessage(msg);
}
@Override
@@ -260,11 +377,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
IProcessingUpdateStatusCallback callback) {
Objects.requireNonNull(processingState);
Objects.requireNonNull(callback);
- mHandler.executeOrSendMessage(
- obtainMessage(
- OnDeviceSandboxedInferenceService::onUpdateProcessingState,
- OnDeviceSandboxedInferenceService.this, processingState,
+
+ Message msg = Message.obtain(mHandler, MSG_UPDATE_PROCESSING_STATE,
+ new UpdateStateParams(processingState,
wrapOutcomeReceiver(callback)));
+ mHandler.sendMessage(msg);
}
};
}
@@ -471,7 +588,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
IResponseCallback callback) {
return new ProcessingCallback() {
@Override
- public void onResult(@androidx.annotation.NonNull Bundle result) {
+ public void onResult(@NonNull Bundle result) {
try {
callback.onSuccess(result);
} catch (RemoteException e) {
@@ -507,7 +624,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
IStreamingResponseCallback callback) {
return new StreamingProcessingCallback() {
@Override
- public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
+ public void onPartialResult(@NonNull Bundle partialResult) {
try {
callback.onNewContent(partialResult);
} catch (RemoteException e) {
@@ -516,7 +633,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
@Override
- public void onResult(@androidx.annotation.NonNull Bundle result) {
+ public void onResult(@NonNull Bundle result) {
try {
callback.onSuccess(result);
} catch (RemoteException e) {
@@ -549,7 +666,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
+ @NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
@@ -604,7 +721,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service {
@Override
public void onError(
- @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
+ @NonNull OnDeviceIntelligenceException error) {
try {
callback.onFailure(error.getErrorCode(), error.getMessage());
} catch (RemoteException e) {
diff --git a/packages/NeuralNetworks/service/Android.bp b/packages/NeuralNetworks/service/Android.bp
new file mode 100644
index 000000000000..05c603f5ebce
--- /dev/null
+++ b/packages/NeuralNetworks/service/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "service-ondeviceintelligence-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: [
+ "//frameworks/base:__subpackages__",
+ "//packages/modules/NeuralNetworks:__subpackages__",
+ ],
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 7dd8f2fdcecb..2626cc880e09 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -21,6 +21,7 @@ import static android.system.OsConstants.O_ACCMODE;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.PROT_READ;
+import android.annotation.SuppressLint;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
@@ -42,7 +43,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
@@ -50,6 +51,8 @@ import java.util.concurrent.TimeoutException;
/**
* Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
* some known types.
+ *
+ * @hide
*/
public class BundleUtil {
private static final String TAG = "BundleUtil";
@@ -76,7 +79,7 @@ public class BundleUtil {
* {@link ClassNotFoundException} exception is swallowed and `null` is returned
* instead. We want to ensure cleanup of null entries in such case.
*/
- bundle.putObject(key, null);
+ bundle.putParcelable(key, null);
continue;
}
if (canMarshall(obj) || obj instanceof CursorWindow) {
@@ -122,7 +125,7 @@ public class BundleUtil {
* {@link ClassNotFoundException} exception is swallowed and `null` is returned
* instead. We want to ensure cleanup of null entries in such case.
*/
- bundle.putObject(key, null);
+ bundle.putParcelable(key, null);
continue;
}
if (canMarshall(obj)) {
@@ -167,7 +170,7 @@ public class BundleUtil {
* {@link ClassNotFoundException} exception is swallowed and `null` is returned
* instead. We want to ensure cleanup of null entries in such case.
*/
- bundle.putObject(key, null);
+ bundle.putParcelable(key, null);
continue;
}
if (canMarshall(obj)) {
@@ -317,11 +320,16 @@ public class BundleUtil {
};
}
- private static boolean canMarshall(Object obj) {
- return obj instanceof byte[] || obj instanceof PersistableBundle
- || PersistableBundle.isValidType(obj);
+ private static boolean canMarshall(Object value) {
+ return (value instanceof byte[]) || (value instanceof Integer) || (value instanceof Long) ||
+ (value instanceof Double) || (value instanceof String) ||
+ (value instanceof int[]) || (value instanceof long[]) ||
+ (value instanceof double[]) || (value instanceof String[]) ||
+ (value instanceof PersistableBundle) || (value == null) ||
+ (value instanceof Boolean) || (value instanceof boolean[]);
}
+ @SuppressLint("NewApi")
private static void ensureValidBundle(Bundle bundle) {
if (bundle == null) {
throw new IllegalArgumentException("Request passed is expected to be non-null");
@@ -364,7 +372,7 @@ public class BundleUtil {
}
} catch (ErrnoException e) {
throw new BadParcelableException(
- "Invalid File descriptor passed in the Bundle.", e);
+ "Invalid File descriptor passed in the Bundle.");
}
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
index bef3f8048da1..e8a1b322f661 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
@@ -28,6 +28,9 @@ import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
+/**
+ * @hide
+ */
public class InferenceInfoStore {
private static final String TAG = "InferenceInfoStore";
private final TreeSet<InferenceInfo> inferenceInfos;
@@ -98,4 +101,4 @@ public class InferenceInfoStore {
info.startTimeMs).setEndTimeMillis(info.endTimeMs).setSuspendedTimeMillis(
info.suspendedTimeMs).build();
}
-} \ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
index 1450dc0803d6..6badc53ae97e 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
@@ -16,7 +16,21 @@
package com.android.server.ondeviceintelligence;
-public interface OnDeviceIntelligenceManagerInternal {
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+
+/**
+ * Exposes APIs to {@code system_server} components outside of the module boundaries.
+ * <p> This API should be access using {@link com.android.server.LocalManagerRegistry}. </p>
+ *
+ * @hide
+ */
+@SystemApi(client = Client.SYSTEM_SERVER)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
+public interface OnDeviceIntelligenceManagerLocal {
/**
* Gets the uid for the process that is currently hosting the
* {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index b0d69e67dac5..607ec1c71094 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,38 +16,42 @@
package com.android.server.ondeviceintelligence;
+import static android.app.ondeviceintelligence.flags.Flags.enableOnDeviceIntelligenceModule;
+
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS;
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
-import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BROADCAST_INTENT;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
-import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
+import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
import static com.android.server.ondeviceintelligence.BundleUtil.wrapWithValidation;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.AppGlobals;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.ICancellationSignal;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IRemoteCallback;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.InferenceInfo;
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.app.ondeviceintelligence.utils.BinderUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -58,16 +62,12 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -82,17 +82,14 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
-import com.android.internal.os.BackgroundThread;
-import com.android.server.LocalServices;
+import com.android.modules.utils.AndroidFuture;
+import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.ServiceConnector;
+import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
@@ -182,9 +179,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void onStart() {
publishBinderService(
Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
- /* allowIsolated = */true);
- LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
- this::getRemoteInferenceServiceUid);
+ /* allowIsolated = */ true);
+ if (enableOnDeviceIntelligenceModule()) {
+ LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class,
+ this::getRemoteInferenceServiceUid);
+ }
}
@Override
@@ -203,10 +202,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void onUserUnlocked(@NonNull TargetUser user) {
Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle());
//connect to remote services(if available) during boot.
- if(user.getUserHandle().equals(UserHandle.SYSTEM)) {
+ if (user.getUserHandle().equals(UserHandle.SYSTEM)) {
try {
- ensureRemoteInferenceServiceInitialized();
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ false);
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ false);
} catch (Exception e) {
Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
}
@@ -251,7 +250,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
remoteCallback.sendResult(null);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
AndroidFuture future = new AndroidFuture();
@@ -279,7 +278,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -317,7 +316,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -361,7 +360,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -404,7 +403,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -444,7 +443,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -488,7 +487,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -534,7 +533,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -559,20 +558,31 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
@Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
- this, in, out, err, args, callback, resultReceiver);
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return new com.android.server.ondeviceintelligence.OnDeviceIntelligenceShellCommand(
+ OnDeviceIntelligenceManagerService.this).exec(
+ this,
+ in.getFileDescriptor(),
+ out.getFileDescriptor(),
+ err.getFileDescriptor(),
+ args);
}
};
}
- private void ensureRemoteIntelligenceServiceInitialized() {
+ private boolean ensureRemoteIntelligenceServiceInitialized(boolean throwIfServiceInvalid) {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
String serviceName = getServiceNames()[0];
- Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false));
- mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
+ if (!BinderUtils.withCleanCallingIdentity(
+ () -> validateServiceElevated(serviceName, false,
+ throwIfServiceInvalid))) {
+ return false;
+ }
+ mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(
+ mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks(
@@ -591,6 +601,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
});
}
}
+ return true;
}
@NonNull
@@ -604,13 +615,21 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
AndroidFuture<Void> result = null;
try {
sanitizeStateParams(processingState);
- ensureRemoteInferenceServiceInitialized();
- result = mRemoteInferenceService.post(
- service -> service.updateProcessingState(
- processingState, callback));
- result.whenCompleteAsync(
- (c, e) -> BundleUtil.tryCloseResource(processingState),
- resourceClosingExecutor);
+ if (ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */
+ false)) {
+ result = mRemoteInferenceService.post(
+ service -> service.updateProcessingState(
+ processingState, callback));
+ result.whenCompleteAsync(
+ (c, e) -> BundleUtil.tryCloseResource(processingState),
+ resourceClosingExecutor);
+ } else {
+ callback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "Remote service cannot be initialized.");
+ }
+ } catch (RemoteException e) {
+ Slog.w("Failed to invoke updateProcessingState", e);
} finally {
if (result == null) {
resourceClosingExecutor.execute(
@@ -622,11 +641,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
};
}
- private void ensureRemoteInferenceServiceInitialized() {
+ private boolean ensureRemoteInferenceServiceInitialized(boolean throwIfServiceInvalid) {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
String serviceName = getServiceNames()[1];
- Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true));
+ if (!BinderUtils.withCleanCallingIdentity(
+ () -> validateServiceElevated(serviceName, true, throwIfServiceInvalid))) {
+ return false;
+ }
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
@@ -636,7 +658,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void onConnected(
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
- ensureRemoteIntelligenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ return;
+ }
service.registerRemoteStorageService(
getIRemoteStorageService(), new IRemoteCallback.Stub() {
@Override
@@ -659,20 +685,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
@Override
public void onDisconnected(
@NonNull IOnDeviceSandboxedInferenceService service) {
- ensureRemoteIntelligenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ return;
+ }
mRemoteOnDeviceIntelligenceService.run(
IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
}
@Override
public void onBinderDied() {
- ensureRemoteIntelligenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ return;
+ }
mRemoteOnDeviceIntelligenceService.run(
IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected);
}
});
}
}
+ return true;
}
private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
@@ -743,9 +778,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
if (mTemporaryConfigNamespace != null) {
return mTemporaryConfigNamespace;
}
-
- return mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace);
+ return mContext.getResources()
+ .getString(
+ mContext.getResources()
+ .getIdentifier(
+ "config_defaultOnDeviceIntelligenceDeviceConfigNamespace",
+ "string",
+ "android"));
}
}
@@ -759,7 +798,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
Bundle bundle = new Bundle();
bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle);
- ensureRemoteInferenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ return;
+ }
mRemoteInferenceService.run(service -> service.updateProcessingState(bundle,
new IProcessingUpdateStatusCallback.Stub() {
@Override
@@ -782,7 +825,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- ensureRemoteIntelligenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ future.completeExceptionally(new OnDeviceIntelligenceException(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_NOT_AVAILABLE));
+ return;
+ }
AndroidFuture<ParcelFileDescriptor> pfdFuture = new AndroidFuture<>();
mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
@@ -805,7 +854,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature,
@@ -829,40 +878,48 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
};
}
- private void validateServiceElevated(String serviceName, boolean checkIsolated) {
+ private boolean validateServiceElevated(String serviceName, boolean checkIsolated,
+ boolean throwIfServiceInvalid) {
try {
if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalStateException(
- "Remote service is not configured to complete the request");
+ if (throwIfServiceInvalid) {
+ throw new IllegalStateException(
+ "Remote service is not configured to complete the request");
+ }
+ return false;
}
ComponentName serviceComponent = ComponentName.unflattenFromString(
serviceName);
- ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(
serviceComponent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.SYSTEM.getIdentifier());
- if (serviceInfo != null) {
- if (!checkIsolated) {
- checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
- return;
- }
-
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ if (!checkIsolated) {
checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
- if (!isIsolatedService(serviceInfo)) {
- throw new SecurityException(
- "Call required an isolated service, but the configured service: "
- + serviceName + ", is not isolated");
- }
- } else {
+ Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
+ return true;
+ }
+
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
+ if (!isIsolatedService(serviceInfo)) {
+ throw new SecurityException(
+ "Call required an isolated service, but the configured service: "
+ + serviceName + ", is not isolated");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (throwIfServiceInvalid) {
throw new IllegalStateException(
"Remote service is not configured to complete the request.");
}
- } catch (RemoteException e) {
- throw new IllegalStateException("Could not fetch service info for remote services", e);
+ return false;
+ } catch (SecurityException e) {
+ if (throwIfServiceInvalid) {
+ throw e;
+ }
+ return false;
}
+ return true;
}
private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
@@ -870,8 +927,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
final String permission = serviceInfo.permission;
if (!requiredPermission.equals(permission)) {
throw new SecurityException(String.format(
- "Service %s requires %s permission. Found %s permission",
- serviceInfo.getComponentName(),
+ "%s requires %s permission. Found %s permission",
+ serviceInfo,
requiredPermission,
serviceInfo.permission));
}
@@ -909,10 +966,22 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
return mTemporaryServiceNames;
}
}
- return new String[]{mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService),
- mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService)};
+ return new String[]{
+ mContext.getResources()
+ .getString(
+ mContext.getResources()
+ .getIdentifier(
+ "config_defaultOnDeviceIntelligenceService",
+ "string",
+ "android")),
+ mContext.getResources()
+ .getString(
+ mContext.getResources()
+ .getIdentifier(
+ "config_defaultOnDeviceSandboxedInferenceService",
+ "string",
+ "android"))
+ };
}
protected String[] getBroadcastKeys() throws Resources.NotFoundException {
@@ -923,7 +992,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
}
- return new String[]{ MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT };
+ return new String[]{MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT};
}
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
@@ -1068,7 +1137,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
private synchronized Handler getTemporaryHandler() {
if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
synchronized (mLock) {
@@ -1090,10 +1159,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
return mTemporaryHandler;
}
+ // Using #getLong here as the timeout settings are only applicable to the services running in
+ // SYSTEM user only.
+ @SuppressWarnings("NonUserGetterCalled")
private long getIdleTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
- mContext.getUserId());
+ return Settings.Secure.getLong(mContext.getContentResolver(),
+ ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
+ TimeUnit.HOURS.toMillis(1));
}
private int getRemoteInferenceServiceUid() {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index d2c84fa1b18a..c641de8b47b1 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -18,12 +18,16 @@ package com.android.server.ondeviceintelligence;
import android.annotation.NonNull;
import android.os.Binder;
-import android.os.ShellCommand;
+
+import com.android.modules.utils.BasicShellCommandHandler;
import java.io.PrintWriter;
import java.util.Objects;
-final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+/**
+ * @hide
+ */
+final class OnDeviceIntelligenceShellCommand extends BasicShellCommandHandler {
private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
@NonNull
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index ac9747aa83b3..0c43a309c456 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -16,6 +16,7 @@
package com.android.server.ondeviceintelligence;
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
@@ -26,13 +27,15 @@ import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
-import com.android.internal.infra.ServiceConnector;
+import com.android.modules.utils.ServiceConnector;
import java.util.concurrent.TimeUnit;
/**
* Manages the connection to the remote on-device intelligence service. Also, handles unbinding
* logic set by the service implementation via a Secure Settings flag.
+ *
+ * @hide
*/
public class RemoteOnDeviceIntelligenceService extends
ServiceConnector.Impl<IOnDeviceIntelligenceService> {
@@ -56,11 +59,13 @@ public class RemoteOnDeviceIntelligenceService extends
return LONG_TIMEOUT;
}
+ // Using #getLong here as the timeout settings are only applicable to the services running in
+ // SYSTEM user only.
@Override
+ @SuppressWarnings("NonUserGetterCalled")
protected long getAutoDisconnectTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
- TimeUnit.SECONDS.toMillis(30),
- mContext.getUserId());
+ return Settings.Secure.getLong(mContext.getContentResolver(),
+ ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30));
}
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 18b13838ea7c..8c5d5a7ba736 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
package com.android.server.ondeviceintelligence;
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
@@ -26,7 +27,7 @@ import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
-import com.android.internal.infra.ServiceConnector;
+import com.android.modules.utils.ServiceConnector;
import java.util.concurrent.TimeUnit;
@@ -35,6 +36,8 @@ import java.util.concurrent.TimeUnit;
* Manages the connection to the remote on-device sand boxed inference service. Also, handles
* unbinding
* logic set by the service implementation via a SecureSettings flag.
+ *
+ * @hide
*/
public class RemoteOnDeviceSandboxedInferenceService extends
ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
@@ -65,12 +68,13 @@ public class RemoteOnDeviceSandboxedInferenceService extends
return LONG_TIMEOUT;
}
-
+ // Using #getLong here as the timeout settings are only applicable to the services running in
+ // SYSTEM user only.
@Override
+ @SuppressWarnings("NonUserGetterCalled")
protected long getAutoDisconnectTimeoutMs() {
- return Settings.Secure.getLongForUser(mContext.getContentResolver(),
- Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
- TimeUnit.SECONDS.toMillis(30),
- mContext.getUserId());
+ return Settings.Secure.getLong(mContext.getContentResolver(),
+ ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
+ TimeUnit.SECONDS.toMillis(30));
}
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
index 32f0698a8f9c..249bcd37db5d 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
@@ -21,7 +21,7 @@ import android.os.Handler;
import android.os.PersistableBundle;
import android.os.RemoteException;
-import com.android.internal.infra.AndroidFuture;
+import com.android.modules.utils.AndroidFuture;
import java.util.concurrent.TimeoutException;
@@ -32,6 +32,8 @@ import java.util.concurrent.TimeoutException;
* some cases. Instead, in such cases we rely on the remote service sending progress updates and if
* there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
* download will not complete and enabling faster cleanup.
+ *
+ * @hide
*/
public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
private final IDownloadCallback callback;
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 68443a7e1492..e029f3a16066 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -81,10 +81,12 @@
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
- android:exported="false" />
+ android:exported="false"
+ android:enableOnBackInvokedCallback="false" />
<activity android:name=".InstallInstalling"
- android:exported="false" />
+ android:exported="false"
+ android:enableOnBackInvokedCallback="false" />
<receiver android:name=".common.InstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
@@ -138,6 +140,7 @@
<activity android:name=".UninstallUninstalling"
android:excludeFromRecents="true"
+ android:enableOnBackInvokedCallback="false"
android:exported="false" />
<receiver android:name=".UninstallFinish"
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 50db5018d44e..50331014f926 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -45,6 +45,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUpdateSelfTestCases",
"options":[
{
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index 66303edb0dea..77fad551eba7 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -23,19 +23,19 @@
<string name="cancel" msgid="1018267193425558088">"Kanselleer"</string>
<string name="installing" msgid="4921993079741206516">"Installeer tans …"</string>
<string name="installing_app" msgid="1165095864863849422">"Installeer tans <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> …"</string>
- <string name="install_done" msgid="5987363587661783896">"Program geïnstalleer."</string>
+ <string name="install_done" msgid="5987363587661783896">"App geïnstalleer."</string>
<string name="install_confirm_question" msgid="7663733664476363311">"Wil jy hierdie program installeer?"</string>
<string name="install_confirm_question_update" msgid="3348888852318388584">"Wil jy hierdie program opdateer?"</string>
<string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou tablet kry. Appfunksies kan verander.&lt;/p&gt;"</string>
<string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou TV kry. Appfunksies kan verander.&lt;/p&gt;"</string>
<string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksies kan verander.&lt;/p&gt;"</string>
- <string name="install_failed" msgid="5777824004474125469">"Program nie geïnstalleer nie."</string>
+ <string name="install_failed" msgid="5777824004474125469">"App nie geïnstalleer nie."</string>
<string name="install_failed_blocked" msgid="8512284352994752094">"Die installering van die pakket is geblokkeer."</string>
- <string name="install_failed_conflict" msgid="3493184212162521426">"Program is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
- <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Program is nie geïnstalleer nie omdat dit nie met jou tablet versoenbaar is nie."</string>
- <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Hierdie program is nie met jou TV versoenbaar nie."</string>
- <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Program is nie geïnstalleer nie omdat dit nie met jou foon versoenbaar is nie."</string>
- <string name="install_failed_invalid_apk" msgid="8581007676422623930">"Program is nie geïnstalleer nie omdat pakket ongeldig blyk te wees."</string>
+ <string name="install_failed_conflict" msgid="3493184212162521426">"App is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
+ <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"App is nie geïnstalleer nie omdat dit nie met jou tablet versoenbaar is nie."</string>
+ <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Hierdie app is nie met jou TV versoenbaar nie."</string>
+ <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"App is nie geïnstalleer nie omdat dit nie met jou foon versoenbaar is nie."</string>
+ <string name="install_failed_invalid_apk" msgid="8581007676422623930">"App is nie geïnstalleer nie omdat pakket ongeldig blyk te wees."</string>
<string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou tablet geïnstalleer word nie."</string>
<string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou TV geïnstalleer word nie."</string>
<string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou foon geïnstalleer word nie."</string>
@@ -49,25 +49,25 @@
<string name="manage_applications" msgid="5400164782453975580">"Bestuur programme"</string>
<string name="out_of_space_dlg_title" msgid="4156690013884649502">"Geen spasie oor nie"</string>
<string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie geïnstalleer word nie. Maak spasie beskikbaar en probeer weer."</string>
- <string name="app_not_found_dlg_title" msgid="5107924008597470285">"Program nie gevind nie"</string>
- <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Die program is nie in die lys geïnstalleerde programme gevind nie."</string>
+ <string name="app_not_found_dlg_title" msgid="5107924008597470285">"App nie gevind nie"</string>
+ <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Die app is nie in die lys geïnstalleerde programme gevind nie."</string>
<string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Nie toegelaat nie"</string>
<string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"Die huidige gebruiker mag nie hierdie deïnstallering uitvoer nie."</string>
<string name="generic_error_dlg_title" msgid="5863195085927067752">"Fout"</string>
- <string name="generic_error_dlg_text" msgid="5287861443265795232">"Program kon nie gedeïnstalleer word nie."</string>
- <string name="uninstall_application_title" msgid="4045420072401428123">"Deïnstalleer program"</string>
+ <string name="generic_error_dlg_text" msgid="5287861443265795232">"App kon nie gedeïnstalleer word nie."</string>
+ <string name="uninstall_application_title" msgid="4045420072401428123">"Deïnstalleer app"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"Deïnstalleer opdatering"</string>
- <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> is deel van die volgende program:"</string>
+ <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> is deel van die volgende app:"</string>
<string name="uninstall_application_text" msgid="3816830743706143980">"Wil jy hierdie app deïnstalleer?"</string>
<string name="archive_application_text" msgid="8482325710714386348">"Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_all_users" msgid="3151229641681672580">"Argiveer hierdie app vir alle gebruikers? Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Argiveer hierdie app op jou werkprofiel? Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_user" msgid="2586558895535581451">"Argiveer hierdie app vir <xliff:g id="USERNAME">%1$s</xliff:g>? Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Wil jy hierdie app wat in jou privaat ruimte is, argiveer? Jou persoonlike data sal gestoor word"</string>
- <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Wil jy hierdie program vir "<b>"alle"</b>" gebruikers deïnstalleer? Die program en sy data sal van "<b>"alle"</b>" gebruikers op hierdie toestel verwyder word."</string>
- <string name="uninstall_application_text_user" msgid="498072714173920526">"Wil jy hierdie program vir die gebruiker <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer?"</string>
+ <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Wil jy hierdie app vir "<b>"alle"</b>" gebruikers deïnstalleer? Die app en sy data sal van "<b>"alle"</b>" gebruikers op hierdie toestel verwyder word."</string>
+ <string name="uninstall_application_text_user" msgid="498072714173920526">"Wil jy hierdie app vir die gebruiker <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer?"</string>
<string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Wil jy hierdie program op jou werkprofiel deïnstalleer?"</string>
- <string name="uninstall_update_text" msgid="863648314632448705">"Vervang hierdie program met die fabriekweergawe? Alle data sal verwyder word."</string>
+ <string name="uninstall_update_text" msgid="863648314632448705">"Vervang hierdie app met die fabriekweergawe? Alle data sal verwyder word."</string>
<string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Vervang hierdie program met die fabriekweergawe? Alle data sal verwyder word. Dit beïnvloed alle gebruikers van hierdie toestel, insluitend dié met werkprofiele."</string>
<string name="uninstall_keep_data" msgid="7002379587465487550">"Hou <xliff:g id="SIZE">%1$s</xliff:g> se programdata."</string>
<string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Wil jy hierdie app uitvee?"</string>
@@ -85,28 +85,28 @@
<string name="uninstalling_cloned_app" msgid="1826380164974984870">"Vee tans <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-kloon uit …"</string>
<string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Kan nie aktiewe toesteladministrasie-app deïnstalleer nie"</string>
<string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Kan nie aktiewe toesteladministrasie-app vir <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer nie"</string>
- <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Dié program word vir sommige gebruikers of profiele vereis en is vir ander gedeïnstalleer"</string>
- <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Hierdie program is nodig vir jou profiel en kan nie gedeïnstalleer word nie."</string>
- <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Jou toesteladministrateur vereis die program; kan nie gedeïnstalleer word nie."</string>
+ <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Dié app word vir sommige gebruikers of profiele vereis en is vir ander gedeïnstalleer"</string>
+ <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Hierdie app is nodig vir jou profiel en kan nie gedeïnstalleer word nie."</string>
+ <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Jou toesteladministrateur vereis dié app; dit kan nie gedeïnstalleer word nie."</string>
<string name="manage_device_administrators" msgid="3092696419363842816">"Bestuur toesteladministrasie-apps"</string>
<string name="manage_users" msgid="1243995386982560813">"Bestuur gebruikers"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie gedeïnstalleer word nie."</string>
<string name="Parse_error_dlg_text" msgid="1661404001063076789">"Kon nie die pakket ontleed nie."</string>
- <string name="message_staging" msgid="8032722385658438567">"Voer tans program uit …"</string>
+ <string name="message_staging" msgid="8032722385658438567">"Voer tans app uit …"</string>
<string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Jou tablet word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Jou TV word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Jou horlosie word vir jou veiligheid tans nie toegelaat om onbekende apps van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Jou foon word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
- <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
- <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
- <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Jou TV en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou TV of verlies van data wat uit sy gebruik kan spruit."</string>
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Jou TV en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou TV of verlies van data wat uit sy gebruik kan spruit."</string>
<string name="cloned_app_label" msgid="7503612829833756160">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-kloon"</string>
<string name="archiving_app_label" msgid="1127085259724124725">"Argiveer <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>?"</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"Gaan voort"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"Instellings"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"Installeer/deïnstalleer Wear-programme"</string>
- <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Kennisgewing dat program geïnstalleer is"</string>
+ <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Kennisgewing dat app geïnstalleer is"</string>
<string name="notification_installation_success_message" msgid="6450467996056038442">"Suksesvol geïnstalleer"</string>
<string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” is suksesvol geïnstalleer"</string>
<string name="unarchive_application_title" msgid="7958278328280721421">"Stel <xliff:g id="APPNAME">%1$s</xliff:g> terug vanaf <xliff:g id="INSTALLERNAME">%2$s</xliff:g>?"</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 1561e790ff2e..ec6d3fcc185d 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -43,7 +43,7 @@
<string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"L\'amministratore non consente l\'installazione di app ottenute da origini sconosciute"</string>
<string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"Questo utente non può installare app sconosciute"</string>
<string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"L\'utente non dispone dell\'autorizzazione a installare app"</string>
- <string name="ok" msgid="7871959885003339302">"OK"</string>
+ <string name="ok" msgid="7871959885003339302">"Ok"</string>
<string name="archive" msgid="4447791830199354721">"Archivia"</string>
<string name="update_anyway" msgid="8792432341346261969">"Aggiorna comunque"</string>
<string name="manage_applications" msgid="5400164782453975580">"Gestisci app"</string>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
index 38cae307f5dc..7209cc2e86af 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Kies \'n prent"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Neem \'n foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Kies ’n profielprent"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Verstekgebruikerikoon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Klaar"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
index 917a2fdc6a61..713e0d951a39 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ምስል ይምረጡ"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ፎቶ ያንሱ"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"የመገለጫ ሥዕል ይምረጡ"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ነባሪ የተጠቃሚ አዶ"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"ተከናውኗል"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
index 8e5231ba1e77..1b5dbc0248cc 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"اختيار صورة"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"التقاط صورة"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"اختيار صورة للملف الشخصي"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"رمز المستخدم التلقائي"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"تم"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
index c77b573b26c6..628132809831 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"এখন ফট’ তোলক"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"কৰা হ’ল"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
index 6871473a82a3..d47ffceae5d8 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Şəkil seçin"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Foto çəkin"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profil şəkli seçin"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Defolt istifadəçi ikonası"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Hazırdır"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
index 42fb478f5ca0..886bd5d946dd 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Slikajte"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite sliku profila"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Podrazumevana ikona korisnika"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gotovo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
index c2ef4db67c5d..38a1340d17d5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Выбраць відарыс"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Зрабіць фота"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Выберыце відарыс профілю"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Стандартны карыстальніцкі значок"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Гатова"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
index eeca7d8d5c9e..90e78b5051c2 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Избиране на изображение"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Правене на снимка"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Изберете снимка на потребителския профил"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Икона за основния потребител"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Готово"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
index 6d4d29f47ac6..c56c9dae178c 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"একটি ছবি বেছে নিন"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ফটো তুলুন"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"কোনও একটি প্রোফাইল ছবি বেছে নিন"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ডিফল্ট ব্যবহারকারীর আইকন"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"হয়ে গেছে"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
index def1b4525c40..1dd707dd5f13 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Snimite fotografiju"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite sliku profila"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Zadana ikona korisnika"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gotovo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
index 1b613cafa4df..d8d3171a7b94 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Tria una imatge"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Fes una foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Tria una foto de perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icona d\'usuari predeterminat"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Fet"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
index ffe2f47df489..6ee5b3e56f66 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Zvolit obrázek"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Vyfotit"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Vyberte profilový obrázek"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Výchozí uživatelská ikona"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Hotovo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
index ab01eef4ab6e..0583399e3ef7 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Vælg et billede"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Tag et billede"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Vælg et profilbillede"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikon for standardbruger"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Udfør"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
index 4d6c651adabd..345b4faff396 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Bild auswählen"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Foto aufnehmen"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profilbild auswählen"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Standardmäßiges Nutzersymbol"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Fertig"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
index 9e6813fff4d0..7cecd45dc11f 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Επιλέξτε μια εικόνα"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Λήψη φωτογραφίας"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Επιλογή φωτογραφ­ίας προφίλ"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Προεπιλεγμένο εικονίδιο χρήστη"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Τέλος"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
index d0aae7917ffd..f9905d0eb796 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Done"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
index d0aae7917ffd..f9905d0eb796 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Done"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
index d0aae7917ffd..f9905d0eb796 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Done"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
index 296dcc8603e6..8e9d407bd994 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Elegir una imagen"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Tomar una foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Elige una foto de perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ícono de usuario predeterminado"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Listo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
index 5d4a8d280796..a7da134da528 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Seleccionar una imagen"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Hacer una foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Elige una imagen de perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icono de usuario predeterminado"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Hecho"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
index ecd3a83ea2b2..ecf140da20be 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Vali pilt"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Pildista"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profiilipildi valimine"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Vaikekasutajaikoon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Valmis"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
index e29bc114b5ec..c55d559738a2 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Aukeratu irudi bat"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Atera argazki bat"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Aukeratu profileko argazki bat"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Erabiltzaile lehenetsiaren ikonoa"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Eginda"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
index 25efac4ba93d..7db55cfa5fc7 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"انتخاب تصویر"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"عکس گرفتن"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"انتخاب عکس نمایه"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"نماد کاربر پیش‌فرض"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"تمام"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
index 4d805ed242cb..7a0cca10c5b4 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Valitse kuva"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Ota kuva"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Valitse profiilikuva"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Oletuskäyttäjäkuvake"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Valmis"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
index fb32c5b118fb..e43947111fa9 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Sélectionner une image"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Prendre une photo"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Choisir une photo de profil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icône d\'utilisateur par défaut"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Terminé"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
index 79620e96bcac..2ffeb4365e36 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Choisir une image"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Prendre une photo"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Choisir une photo de profil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icône de l\'utilisateur par défaut"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"OK"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
index bbfa18bf0080..5e46474eb195 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Escoller unha imaxe"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Tirar unha foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Escoller unha imaxe do perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icona do usuario predeterminado"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Feito"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
index 247afe13d0fb..96e67530cf90 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"છબી પસંદ કરો"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ફોટો લો"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"પ્રોફાઇલ ફોટો પસંદ કરો"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ડિફૉલ્ટ વપરાશકર્તાનું આઇકન"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"થઈ ગયું"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
index 3c515769630e..0878ff22f20e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"इमेज चुनें"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"फ़ोटो खींचें"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"प्रोफ़ाइल फ़ोटो चुनें"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"उपयोगकर्ता के लिए डिफ़ॉल्ट आइकॉन"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"हो गया"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
index e9a56bdc839e..b599ddf42977 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Snimi fotografiju"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite profilnu sliku"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikona zadanog korisnika"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gotovo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
index f3d53cd57d53..55f4a6098f96 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Kép kiválasztása"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Fotó készítése"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profilkép választása"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Alapértelmezett felhasználó ikonja"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Kész"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
index bfa0d935c6e7..9b5cbf3717de 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Pilih gambar"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Ambil foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Pilih foto profil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikon pengguna default"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Selesai"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
index d49652480db5..4007c385c8a4 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Velja mynd"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Taka mynd"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Veldu prófílmynd"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Tákn sjálfgefins notanda"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Lokið"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
index 3ba0a01cbed2..caeb93b79204 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Scegli un\'immagine"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Scatta una foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Scegli un\'immagine del profilo"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Icona dell\'utente predefinito"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Fine"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
index 903989d999e0..03ca68eec0be 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"לבחירת תמונה"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"צילום תמונה"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"בחירה של תמונת הפרופיל"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"סמל המשתמש שמוגדר כברירת מחדל"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"סיום"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
index 817180bb6493..ae61afcba50d 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"სურათის არჩევა"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ფოტოს გადაღება"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"პროფილის სურათის არჩევა"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"მომხმარებლის ნაგულისხმევი ხატულა"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"მზადაა"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
index ec01801bace9..f1e8950cd1e7 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Кескін таңдау"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Суретке түсіру"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Профиль суретін таңдау"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Әдепкі пайдаланушы белгішесі"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Дайын"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
index 7a311224646e..a86491e3bb7d 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ជ្រើសរើស​រូបភាព"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ថតរូប"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"ជ្រើសរើសរូបភាពកម្រងព័ត៌មាន"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"រួចរាល់"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
index 45bfb532e6e1..682ebfd827ac 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"이미지 선택"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"사진 찍기"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"프로필 사진 선택"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"기본 사용자 아이콘"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"완료"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
index b3d39de49bda..3d8e7bff7396 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Сүрөт тандоо"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Сүрөткө тартуу"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Профилдин сүрөтүн тандоо"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Демейки колдонуучунун сүрөтчөсү"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Бүттү"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
index 3ba6709d04a4..64bd871481d5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ເລືອກຮູບ"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ຖ່າຍຮູບ"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"ເລືອກຮູບໂປຣໄຟລ໌"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"ແລ້ວໆ"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
index b0c0c39495f5..4bc183b5cd4c 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Pasirinkti vaizdą"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Fotografuoti"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profilio nuotraukos pasirinkimas"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Numatytojo naudotojo piktograma"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Atlikta"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
index e664c80b5e7c..72e6ec438389 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Izvēlēties attēlu"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Uzņemt fotoattēlu"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profila attēla izvēle"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Noklusējuma lietotāja ikona"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gatavs"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
index bb0e2476bdc2..4b6939ec7775 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Изберете слика"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Фотографирај"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Изберете профилна слика"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Икона за стандарден корисник"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Готово"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
index 674b4f5d8fb5..aec39b13074c 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"പ്രൊഫൈൽ ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ഡിഫോൾട്ട് ഉപയോക്തൃ ഐക്കൺ"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"പൂർത്തിയായി"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
index 61553b54d745..4d13e871aa64 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Зураг сонгох"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Зураг авах"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Профайл зураг сонгох"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Өгөгдмөл хэрэглэгчийн дүрс тэмдэг"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Болсон"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
index 25996969e61e..45f1b36e9332 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Pilih imej"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Ambil foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Pilih gambar profil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikon pengguna lalai"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Selesai"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
index f9f697b3c200..697308460b15 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ပုံရွေးရန်"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ဓာတ်ပုံရိုက်ရန်"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"ပရိုဖိုင်ပုံ ရွေးပါ"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"မူရင်းအသုံးပြုသူ သင်္ကေတ"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"ပြီးပြီ"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
index c1b7631fa8b2..8a5500665a57 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Velg et bilde"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Ta et bilde"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Velg et profilbilde"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Standard brukerikon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Ferdig"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
index a8c02b97cac8..8373058d7fd5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"कुनै फोटो छनौट गर्नुहोस्"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"फोटो खिच्नुहोस्"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"प्रोफाइल फोटो छान्नुहोस्"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"प्रयोगकर्ताको डिफल्ट आइकन"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"सम्पन्न भयो"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
index 47352bc12100..cb3bd51cb9d4 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Een afbeelding kiezen"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Een foto maken"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Kies een profielfoto"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Standaard gebruikersicoon"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Klaar"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
index 132b97a6f46c..dd54ac304a24 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"ଗୋଟିଏ ଛବି ବାଛନ୍ତୁ"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ଡିଫଲ୍ଟ ୟୁଜର ଆଇକନ"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"ହୋଇଗଲା"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
index 7db79047047b..35787c91d141 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Wybierz obraz"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Zrób zdjęcie"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Wybierz zdjęcie profilowe"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikona domyślnego użytkownika"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gotowe"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
index ae3e6e508c8b..ba1ce2ad34e1 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Escolher uma imagem"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Tirar uma foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Escolha uma foto de perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ícone de usuário padrão"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Concluir"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
index ae3e6e508c8b..ba1ce2ad34e1 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Escolher uma imagem"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Tirar uma foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Escolha uma foto de perfil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ícone de usuário padrão"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Concluir"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
index ce662d41a113..51bcd5794cb9 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Alege o imagine"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Fă o fotografie"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Alege o fotografie de profil"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Pictograma prestabilită a utilizatorului"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Gata"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
index 47f8a839beb4..59fb913841e8 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Выбрать фото"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Сделать снимок"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Выберите фото профиля"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Значок пользователя по умолчанию"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Готово"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
index aaba44266bb1..db7e0b8e4b65 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"රූපයක් තෝරන්න"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ඡායාරූපයක් ගන්න"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"පැතිකඩ පින්තූරයක් තේරීම"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"පෙරනිමි පරිශීලක නිරූපකය"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"නිමයි"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
index 3f801a36843d..36e94f3c5ed6 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Vybrať obrázok"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Odfotiť"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Vyberte profilovú fotku"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Predvolená ikona používateľa"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Hotovo"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
index 0b8a58d0d5d5..0093380052db 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Zgjidh një imazh"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Bëj një fotografi"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Zgjidh një fotografi profili"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikona e parazgjedhur e përdoruesit"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"U krye"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
index 1014df4f3b95..971d8c567cdc 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Одаберите слику"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Сликајте"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Одаберите слику профила"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Подразумевана икона корисника"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Готово"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
index 82034092288a..9196e2ac2adc 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Välj en bild"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Ta ett foto"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Välj en profilbild"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Ikon för standardanvändare"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Klar"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
index b0e8c44d5e8a..a0bc5548f6fe 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Chagua picha"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Piga picha"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Chagua picha ya wasifu"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Aikoni chaguomsingi ya mtumiaji"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Nimemaliza"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
index 31e753e04cc1..b8b4324878af 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"เลือกรูปภาพ"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"ถ่ายรูป"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"เลือกรูปโปรไฟล์"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"ไอคอนผู้ใช้เริ่มต้น"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"เสร็จสิ้น"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
index 26d96c22c7f2..3e10257d6b24 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Resim seç"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Fotoğraf çek"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Profil fotoğrafı seçin"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Varsayılan kullanıcı simgesi"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Bitti"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
index 1fbe5fb1076a..ee4a2fc22960 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Вибрати зображення"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Зробити фото"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Виберіть зображення профілю"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Значок користувача за умовчанням"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Готово"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
index 036c0065ebd7..c6b4ef5ef050 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Chọn hình ảnh"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Chụp ảnh"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Chọn một ảnh hồ sơ"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Biểu tượng người dùng mặc định"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Xong"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
index 58488b35b0a6..684449f446e1 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"选择图片"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"拍照"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"选择个人资料照片"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"默认用户图标"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"完成"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
index e5f375205844..e0d405272dc8 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"選擇圖片"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"拍攝相片"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"選擇個人檔案相片"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"預設使用者圖示"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"完成"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
index 4a58180ba15f..d82ad7e43417 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"選擇圖片"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"拍照"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"選擇個人資料相片"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"預設使用者圖示"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"完成"</string>
</resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
index ad15f890cd05..8678332d8b54 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
@@ -19,9 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="user_image_choose_photo" msgid="5630717762469961028">"Khetha isithombe"</string>
<string name="user_image_take_photo" msgid="3147097821937166738">"Thatha isithombe"</string>
- <!-- no translation found for avatar_picker_title (7478146965334560463) -->
- <skip />
+ <string name="avatar_picker_title" msgid="7478146965334560463">"Khetha isithombe sephrofayela"</string>
<string name="default_user_icon_description" msgid="6018582161341388812">"Isithonjana somsebenzisi sokuzenzakalelayo"</string>
- <!-- no translation found for done (3587741621903511576) -->
- <skip />
+ <string name="done" msgid="3587741621903511576">"Kwenziwe"</string>
</resources>
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index cd03dd7ca1b3..07b1c9e3385e 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -22,6 +22,7 @@ import androidx.collection.MutableScatterMap
import com.google.errorprone.annotations.CanIgnoreReturnValue
import java.util.WeakHashMap
import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
/**
* Callback to be informed of changes in [KeyedObservable] object.
@@ -203,13 +204,71 @@ open class KeyedDataObservable<K> : KeyedObservable<K> {
}
}
- fun hasAnyObserver(): Boolean {
+ open fun hasAnyObserver(): Boolean {
synchronized(observers) { if (observers.isNotEmpty()) return true }
synchronized(keyedObservers) { if (keyedObservers.isNotEmpty()) return true }
return false
}
}
+/** [KeyedDataObservable] that maintains a counter for the observers. */
+abstract class AbstractKeyedDataObservable<K> : KeyedDataObservable<K>() {
+ /**
+ * Counter of observers.
+ *
+ * The value is accurate only when [addObserver] and [removeObserver] are invoked in pairs.
+ */
+ private val counter = AtomicInteger()
+
+ override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) =
+ if (super.addObserver(observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) =
+ if (super.addObserver(key, observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverAdded() {
+ if (counter.getAndIncrement() == 0) onFirstObserverAdded()
+ }
+
+ /** Callbacks when the first observer is just added. */
+ protected abstract fun onFirstObserverAdded()
+
+ override fun removeObserver(observer: KeyedObserver<K?>) =
+ if (super.removeObserver(observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ override fun removeObserver(key: K, observer: KeyedObserver<K>) =
+ if (super.removeObserver(key, observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverRemoved() {
+ if (counter.decrementAndGet() == 0) onLastObserverRemoved()
+ }
+
+ /** Callbacks when the last observer is just removed. */
+ protected abstract fun onLastObserverRemoved()
+
+ override fun hasAnyObserver() = counter.get() > 0
+}
+
/** [KeyedObservable] with no-op implementations for all interfaces. */
open class NoOpKeyedObservable<K> : KeyedObservable<K> {
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
index 04d4bfe0741d..d6e7a896eb63 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
@@ -20,21 +20,10 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
import android.util.Log
-import java.util.concurrent.Executor
-import java.util.concurrent.atomic.AtomicInteger
/** Base class of the Settings provider data stores. */
abstract class SettingsStore(protected val contentResolver: ContentResolver) :
- KeyedDataObservable<String>(), KeyValueStore {
-
- /**
- * Counter of observers.
- *
- * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When
- * an observer is not removed (and its weak reference is garbage collected), the content
- * observer is not unregistered but this is not a big deal.
- */
- private val counter = AtomicInteger()
+ AbstractKeyedDataObservable<String>(), KeyValueStore {
private val contentObserver =
object : ContentObserver(HandlerExecutor.main) {
@@ -48,49 +37,15 @@ abstract class SettingsStore(protected val contentResolver: ContentResolver) :
}
}
- override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
- if (super.addObserver(observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
-
- override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
- if (super.addObserver(key, observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
+ /** The URI to watch for any key change. */
+ protected abstract val uri: Uri
- private fun onObserverAdded() {
- if (counter.getAndIncrement() != 0) return
+ override fun onFirstObserverAdded() {
Log.i(tag, "registerContentObserver")
contentResolver.registerContentObserver(uri, true, contentObserver)
}
- /** The URI to watch for any key change. */
- protected abstract val uri: Uri
-
- override fun removeObserver(observer: KeyedObserver<String?>) =
- if (super.removeObserver(observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- override fun removeObserver(key: String, observer: KeyedObserver<String>) =
- if (super.removeObserver(key, observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- private fun onObserverRemoved() {
- if (counter.decrementAndGet() != 0) return
+ override fun onLastObserverRemoved() {
Log.i(tag, "unregisterContentObserver")
contentResolver.unregisterContentObserver(contentObserver)
}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index 6b93cd73164f..f611793b619b 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -79,6 +79,8 @@ message PreferenceProto {
optional IntentProto launch_intent = 14;
// Descriptor of the preference value.
optional PreferenceValueDescriptorProto value_descriptor = 15;
+ // Indicate how sensitive of the preference.
+ optional int32 sensitivity_level = 16;
// Target of an Intent
message ActionTarget {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index a65d24cf3d6f..a768b5edb395 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -55,9 +55,9 @@ import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
-import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import java.util.Locale
private const val TAG = "PreferenceGraphBuilder"
@@ -387,6 +387,7 @@ fun PreferenceMetadata.toProto(
}
persistent = metadata.isPersistent(context)
if (persistent) {
+ if (metadata is PersistentPreference<*>) sensitivityLevel = metadata.sensitivityLevel
if (
flags.includeValue() &&
enabled &&
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 bc62e565599c..83858d9c9c54 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -141,7 +141,7 @@ public class MainSwitchPreference extends TwoStatePreference
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- buttonView.post(() -> super.setChecked(isChecked));
+ super.setChecked(isChecked);
}
/**
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index 6c11e6997fba..668f981e215b 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -42,6 +42,19 @@ annotation class ReadWritePermit {
}
}
+/** Indicates how sensitive of the data. */
+@Retention(AnnotationRetention.SOURCE)
+@Target(AnnotationTarget.TYPE)
+annotation class SensitivityLevel {
+ companion object {
+ const val UNKNOWN_SENSITIVITY = 0
+ const val NO_SENSITIVITY = 1
+ const val LOW_SENSITIVITY = 2
+ const val MEDIUM_SENSITIVITY = 3
+ const val HIGH_SENSITIVITY = 4
+ }
+}
+
/** Preference interface that has a value persisted in datastore. */
interface PersistentPreference<T> {
@@ -86,6 +99,10 @@ interface PersistentPreference<T> {
callingUid,
this as PreferenceMetadata,
)
+
+ /** The sensitivity level of the preference. */
+ val sensitivityLevel: @SensitivityLevel Int
+ get() = SensitivityLevel.UNKNOWN_SENSITIVITY
}
/** Descriptor of values. */
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index 386b6d94c631..6e86fa7312cf 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -175,7 +175,12 @@ interface PreferenceMetadata {
/** Metadata of preference group. */
@AnyThread
-open class PreferenceGroup(override val key: String, override val title: Int) : PreferenceMetadata
+interface PreferenceGroup : PreferenceMetadata
+
+/** Metadata of preference category. */
+@AnyThread
+open class PreferenceCategory(override val key: String, override val title: Int) :
+ PreferenceGroup
/** Metadata of preference screen. */
@AnyThread
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
index a3709c109111..6704ecc93891 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Bundle
+import androidx.lifecycle.LifecycleCoroutineScope
+import kotlinx.coroutines.CoroutineScope
/**
* Interface to provide dynamic preference title.
@@ -123,7 +125,12 @@ interface PreferenceLifecycleProvider {
*
* @return true if the result is handled
*/
- fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean = false
+ fun onActivityResult(
+ context: PreferenceLifecycleContext,
+ requestCode: Int,
+ resultCode: Int,
+ data: Intent?,
+ ): Boolean = false
}
/**
@@ -133,9 +140,23 @@ interface PreferenceLifecycleProvider {
*/
abstract class PreferenceLifecycleContext(context: Context) : ContextWrapper(context) {
+ /**
+ * [CoroutineScope] tied to the lifecycle, which is cancelled when the lifecycle is destroyed.
+ *
+ * @see [androidx.lifecycle.lifecycleScope]
+ */
+ abstract val lifecycleScope: LifecycleCoroutineScope
+
/** Returns the preference widget object associated with given key. */
abstract fun <T> findPreference(key: String): T?
+ /**
+ * Returns the preference widget object associated with given key.
+ *
+ * @throws NullPointerException if preference is not found
+ */
+ abstract fun <T : Any> requirePreference(key: String): T
+
/** Notifies that preference state of given key is changed and updates preference widget UI. */
abstract fun notifyPreferenceChange(key: String)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 51b9aac029a5..6287fda86a73 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -18,7 +18,7 @@ package com.android.settingslib.preference
import androidx.preference.Preference
import com.android.settingslib.metadata.MainSwitchPreference
-import com.android.settingslib.metadata.PreferenceGroup
+import com.android.settingslib.metadata.PreferenceCategory
import com.android.settingslib.metadata.PreferenceHierarchyNode
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.SwitchPreference
@@ -59,7 +59,7 @@ open class DefaultPreferenceBindingFactory : PreferenceBindingFactory {
metadata as? PreferenceBinding
?: when (metadata) {
is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
- is PreferenceGroup -> PreferenceGroupBinding.INSTANCE
+ is PreferenceCategory -> PreferenceCategoryBinding.INSTANCE
is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
else -> DefaultPreferenceBinding
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index 762f5eaf6325..bd5d17cb2468 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -55,13 +55,13 @@ interface PreferenceScreenBinding : PreferenceBinding {
}
}
-/** Binding of preference group associated with [PreferenceCategory]. */
-interface PreferenceGroupBinding : PreferenceBinding {
+/** Binding of preference category associated with [PreferenceCategory]. */
+interface PreferenceCategoryBinding : PreferenceBinding {
override fun createWidget(context: Context) = PreferenceCategory(context)
companion object {
- @JvmStatic val INSTANCE = object : PreferenceGroupBinding {}
+ @JvmStatic val INSTANCE = object : PreferenceCategoryBinding {}
}
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 7cec59c9090c..6fc9357e9332 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -19,6 +19,8 @@ package com.android.settingslib.preference
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.PreferenceGroup
@@ -57,9 +59,14 @@ class PreferenceScreenBindingHelper(
private val preferenceLifecycleContext =
object : PreferenceLifecycleContext(context) {
+ override val lifecycleScope: LifecycleCoroutineScope
+ get() = fragment.lifecycleScope
+
override fun <T> findPreference(key: String) =
preferenceScreen.findPreference(key) as T?
+ override fun <T : Any> requirePreference(key: String) = findPreference<T>(key)!!
+
override fun notifyPreferenceChange(key: String) =
notifyChange(key, CHANGE_REASON_STATE)
@@ -192,8 +199,8 @@ class PreferenceScreenBindingHelper(
}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- for (preference in lifecycleAwarePreferences) {
- if (preference.onActivityResult(requestCode, resultCode, data)) break
+ lifecycleAwarePreferences.firstOrNull {
+ it.onActivityResult(preferenceLifecycleContext, requestCode, resultCode, data)
}
}
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
index 9d3092d39d03..b41ec957f12d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Geaktiveer deur administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Gedeaktiveer deur administrateur"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Geaktiveer deur Gevorderde Beskerming"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gedeaktiveer deur Gevorderde Beskerming"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
index 9617acaa0c14..8e9488453032 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"በአስተዳዳሪ ነቅቷል"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"በአስተዳዳሪ ተሰናክሏል"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"በላቀ ጥበቃ የነቃ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"በላቀ ጥበቃ የተሰናከለ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
index 581b91458c1c..8b2ccdfbb0e8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"يفعِّل المشرف هذا الإعداد."</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"أوقف المشرف هذا الإعداد"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"تم التفعيل من خلال ميزة \"الحماية المتقدّمة\""</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"تم الإيقاف من خلال ميزة \"الحماية المتقدّمة\""</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
index 5824abdf921c..03e9e828534a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"প্ৰশাসকে সক্ষম কৰিছে"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"প্ৰশাসকে অক্ষম কৰিছে"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"সুৰক্ষা সম্পৰ্কীয় উন্নত সুবিধাটোৱে সক্ষম কৰিছে"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"সুৰক্ষা সম্পৰ্কীয় উন্নত সুবিধাটোৱে অক্ষম কৰিছে"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
index f07e05403afb..98447166a156 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Admin tərəfindən aktiv edildi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Admin tərəfindən deaktiv edildi"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Qabaqcıl Qoruma tərəfindən aktiv edilib"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Qabaqcıl Qoruma tərəfindən deaktiv edilib"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
index e09afbfbbd63..c7b9be28fb1e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administrator je omogućio"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administrator je onemogućio"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućila je Napredna zaštita"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućila je Napredna zaštita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
index a64734bde002..92ed11157dfc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Уключана адміністратарам"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Адключана адміністратарам"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Уключана Палепшанай абаронай"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Адключана Палепшанай абаронай"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
index ccaa6563cbe9..57b50c5c580c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Активирано от администратора"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Деактивирано от администратора"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Активирано от „Разширена защита“"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Деактивирано от „Разширена защита“"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
index 0a48aa21a36b..939ceb82ab40 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"অ্যাডমিন চালু করেছেন"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"অ্যাডমিন বন্ধ করেছেন"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"উন্নত সুরক্ষা চালু করেছে"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"উন্নত সুরক্ষা বন্ধ করেছে"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
index eebcebffe2a1..87cd3b8905c7 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućeno je Naprednom zaštitom"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućeno je Naprednom zaštitom"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
index 51e3fa9c8a41..34099b6f3f08 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activat per l\'administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desactivat per l\'administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activat per la Protecció avançada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Desactivat per la Protecció avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
index a5db6099c8cb..82cd56f39059 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Zapnuto administrátorem"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Vypnuto administrátorem"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivováno pokročilou ochranou"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivováno pokročilou ochranou"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
index 7f10edf158b6..7f7ae8b3d5e0 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktiveret af administratoren"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Deaktiveret af administrator"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktiveret af Avanceret beskyttelse"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktiveret af Avanceret beskyttelse"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
index 604593bf0966..efaa50efacf4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Vom Administrator aktiviert"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Vom Administrator deaktiviert"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Vom erweiterten Sicherheitsprogramm aktiviert"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Vom erweiterten Sicherheitsprogramm deaktiviert"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
index 79b401607a94..ddde3ece472a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ενεργοποιήθηκε από τον διαχειριστή"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Απενεργοποιήθηκε από τον διαχειριστή"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Ενεργοποιήθηκε από την Ενισχυμένη προστασία"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Απενεργοποιήθηκε από την Ενισχυμένη προστασία"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
index 14b92720487e..6a07741d8e4c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
index 14b92720487e..6a07741d8e4c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
index 14b92720487e..6a07741d8e4c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
index 14b92720487e..6a07741d8e4c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Enabled by Advanced Protection"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disabled by Advanced Protection"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
index 616b568d58fe..8dc15f77f6e0 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"El administrador habilitó la opción"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"El administrador inhabilitó la opción"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Habilitado por la Protección avanzada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inhabilitado por la Protección avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
index 351f16cb1a24..7c9864d7e81e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Habilitado por el administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Inhabilitado por el administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Habilitado por Protección Avanzada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inhabilitado por Protección Avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
index c59d6459789e..5939b584a0f2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administraatori lubatud"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administraatori keelatud"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Lubatud täiustatud kaitsega"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Keelatud täiustatud kaitsega"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
index 2a881247c3af..27bef6e92073 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administratzaileak gaitu egin du"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administratzaileak desgaitu du"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Babes aurreratua programak gaitu du"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Babes aurreratua programak desgaitu du"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
index 9c39f98aab17..8fb2646821dc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"توسط سرپرست فعال شده"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"توسط سرپرست غیرفعال شده"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"فعال‌شده با «محافظت پیشرفته»"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"غیرفعال‌شده با «محافظت پیشرفته»"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
index 41fef5af7033..cd7cbd3d83ca 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Järjestelmänvalvojan sallima"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Järjestelmänvalvojan estämä"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Lisäsuojaus on ottanut asetuksen käyttöön"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Lisäsuojaus on poistanut asetuksen käytöstä"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
index 9ff117427555..8f0d572aa193 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activé par la protection avancée"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Désactivé par la protection avancée"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
index 9ff117427555..2215af24664c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activé par la Protection Avancée"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Désactivé par la Protection Avancée"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
index dbf8f7d83b6c..92f33bbf2533 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Opción activada polo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Opción desactivada polo administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Opción activada por Protección avanzada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Opción desactivada por Protección avanzada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
index 4fc4ab48059d..026bdb1127d3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ઍડમિને બંધ કરેલું"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"અદ્યતન સુરક્ષા દ્વારા ચાલુ કરવામાં આવી છે"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"અદ્યતન સુરક્ષા દ્વારા બંધ કરવામાં આવી છે"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
index b5534b9c9246..8fc8fd04ad93 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
@@ -18,5 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"एडमिन की ओर से चालू किया गया"</string>
- <string name="disabled_by_admin" msgid="4023569940620832713">"एडमिन ने यह सुविधा बंद की है"</string>
+ <string name="disabled_by_admin" msgid="4023569940620832713">"एडमिन ने यह सुविधा बंद की हुई है"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"\'ऐडवांस सुरक्षा\' सेटिंग ने चालू किया है"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"\'ऐडवांस सुरक्षा\' सेटिंग ने बंद किया है"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
index eebcebffe2a1..40605a3b83fb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogućila je napredna zaštita"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogućila je napredna zaštita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
index 2ab8142d7610..59135a42fd2a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
@@ -18,5 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"A rendszergazda bekapcsolta"</string>
- <string name="disabled_by_admin" msgid="4023569940620832713">"A rendszergazda kikapcsolta"</string>
+ <string name="disabled_by_admin" msgid="4023569940620832713">"A rendszergazda letiltotta"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Engedélyezte a Speciális védelem"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Letiltotta a Speciális védelem"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
index 23a2a6b125eb..0221f9333249 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Միացված է ադմինիստրատորի կողմից"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Անջատվել է ադմինիստրատորի կողմից"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Միացվել է Լրացուցիչ պաշտպանության կողմից"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Անջատվել է Լրացուցիչ պաշտպանության կողմից"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
index ae8ec8251b7d..6beb4c9b2c26 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Diaktifkan oleh admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dinonaktifkan oleh admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Diaktifkan oleh Perlindungan Lanjutan"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dinonaktifkan oleh Perlindungan Lanjutan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
index 55380b333edd..feb325bf2210 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Gert virkt af kerfisstjóra"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Gert óvirkt af kerfisstjóra"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Virkjað af ítarlegri vernd"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gert óvirkt af ítarlegri vernd"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
index bddf43ce6917..616392026b55 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Attivata dalla protezione avanzata"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Disattivata dalla protezione avanzata"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
index 007de061fbe9..c342041f6c6e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"מופעל על ידי מנהל המכשיר"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"האפשרות הושבתה על ידי האדמין"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ההעדפה הופעלה על ידי ההגנה המתקדמת"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ההעדפה הושבתה על ידי ההגנה המתקדמת"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
index 490efd099569..bd386f5858a9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"管理者によって有効にされています"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"管理者により無効にされています"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"高度な保護機能により有効になっています"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"高度な保護機能により無効になっています"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
index 5c394b832af9..a6fde9022a21 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ჩართულია ადმინისტრატორის მიერ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"გათიშულია ადმინისტრატორის მიერ"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ჩართულია დამატებითი დაცვის საშუალებით"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"გათიშულია დამატებითი დაცვის საშუალებით"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
index eff7e44c6a39..ed0f95c200f6 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Әкімші қосқан"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Әкімші өшірген"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Күшейтілген қорғаныс параметрі қосып қойды."</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Күшейтілген қорғаныс параметрі өшіріп тастады."</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
index 5a4f0748c796..f2f5ab8e1dbf 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"បើកដោយ​អ្នកគ្រប់គ្រង"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"បានបិទដោយអ្នកគ្រប់គ្រង"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"បានបើកដោយការ​ការពារ​កម្រិតខ្ពស់"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"បានបិទដោយការ​ការពារ​កម្រិតខ្ពស់"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
index 9b7a0d8b97bd..ebc41a52b4df 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ನಿರ್ವಾಹಕರು ಸಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ಸುಧಾರಿತ ಸಂರಕ್ಷಣೆ ಮೂಲಕ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ಸುಧಾರಿತ ಸಂರಕ್ಷಣೆ ಮೂಲಕ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
index d4f134cf3adb..552662b4be95 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"관리자가 사용 설정함"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"관리자가 사용 중지함"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"고급 보호 기능으로 사용 설정됨"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"고급 보호 기능으로 사용 중지됨"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
index a934b51f6a98..375ea19ff66e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Администратор иштетип койгон"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Администратор өчүрүп койгон"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Өркүндөтүлгөн коргоо тарабынан иштетилди"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Өркүндөтүлгөн коргоо тарабынан өчүрүлдү"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
index c2d80f28130e..4b311c0ce38d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ເປີດນຳໃຊ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ຖືກຜູ້ເບິ່ງແຍງລະບົບປິດໄວ້"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ໄດ້ເປີດການນຳໃຊ້ໂດຍການປົກປ້ອງຂັ້ນສູງ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ໄດ້ປິດການນຳໃຊ້ໂດຍການປົກປ້ອງຂັ້ນສູງ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
index 2e96a0ad7dae..cbbe92374854 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Įgalino administratorius"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Išjungė administratorius"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Įgalino Papildoma apsauga"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Išjungė Papildoma apsauga"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
index 1d2bcb0d484b..a5189aa70ff8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Iespējoja administrators"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Atspējoja administrators"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Iespējota iestatījuma “Papildu aizsardzība” dēļ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Atspējota iestatījuma “Papildu aizsardzība” dēļ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
index 1c8f1d1a0c43..993b4aea8b13 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Овозможено од администраторот"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Оневозможено од администраторот"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Овозможено од „Напредна заштита“"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Оневозможено од „Напредна заштита“"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
index c4ee22491659..9deeb6aa2cfd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"അഡ്‌മിൻ പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"അഡ്‌മിൻ പ്രവർത്തനരഹിതമാക്കി"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"വിപുലമായ പരിരക്ഷ പ്രവർത്തനക്ഷമമാക്കി"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"വിപുലമായ പരിരക്ഷ പ്രവർത്തനരഹിതമാക്കി"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
index 472c50ac36a3..c9a91de26591 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Админ идэвхжүүлсэн"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Админ цуцалсан"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Дэвшилтэт хамгаалалтаар идэвхжүүлсэн"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Дэвшилтэт хамгаалалтаар идэвхгүй болгосон"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
index d01bfc4dea81..ede12424de04 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"अ‍ॅडमिनने सुरू केलेले"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"अ‍ॅडमिनने बंद केलेले"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"प्रगत संरक्षणाद्वारे सुरू केले आहे"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"प्रगत संरक्षणाद्वारे बंद केले आहे"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
index 618ea8c13c37..e8f710a7c909 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Didayakan oleh pentadbir"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dilumpuhkan oleh pentadbir"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Didayakan oleh Perlindungan Lanjutan"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dilumpuhkan oleh Perlindungan Lanjutan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
index e4626000476e..97be99f43291 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"စီမံခန့်ခွဲသူက ဖွင့်ထားသည်"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"အဆင့်မြင့်ကာကွယ်ရေးက ဖွင့်ထားသည်"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"အဆင့်မြင့်ကာကွယ်ရေးက ပိတ်ထားသည်"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
index 509e70b31645..971d1ccd190a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktivert av administratoren"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Deaktivert av administratoren"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivert av Avansert beskyttelse"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivert av Avansert beskyttelse"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
index 15bb85c7b174..3d2a74e3b270 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"प्रशासकद्वारा सक्षम पारिएको"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"एडमिनले अफ गरेको"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"सुरक्षासम्बन्धी उन्नत सुविधाले अन गरेको छ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"सुरक्षासम्बन्धी उन्नत सुविधाले अफ गरेको छ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
index a73deafbc70f..9830363c7f65 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aangezet door beheerder"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Uitgezet door beheerder"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aangezet door Geavanceerde beveiliging"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Uitgezet door Geavanceerde beveiliging"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
index 4ce6460f8b89..2dab1592c296 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ଆଡମିନଙ୍କ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ଆଡମିନଙ୍କ ଦ୍ଵାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ଆଡଭାନ୍ସଡ ପ୍ରୋଟେକସନ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ଆଡଭାନ୍ସଡ ପ୍ରୋଟେକସନ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
index 1a3a133e6c41..12f296a46b2d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"ਅਡਵਾਂਸ ਸੁਰੱਖਿਆ ਵੱਲੋਂ ਚਾਲੂ ਕੀਤੀ ਗਈ"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ਅਡਵਾਂਸ ਸੁਰੱਖਿਆ ਵੱਲੋਂ ਬੰਦ ਕੀਤੀ ਗਈ"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
index 0523e2b23ed3..df7394766339 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Włączone przez administratora"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Wyłączone przez administratora"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Włączone przez Ochronę zaawansowaną"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Wyłączone przez Ochronę zaawansowaną"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
index 908e2fbbff5b..a705ba421910 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Preferência ativada pela Proteção Avançada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Preferência desativada pela Proteção Avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
index 908e2fbbff5b..b01118381478 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Ativado pela Proteção avançada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Desativado pela Proteção avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
index 908e2fbbff5b..a705ba421910 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Preferência ativada pela Proteção Avançada"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Preferência desativada pela Proteção Avançada"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
index ad41605c636f..3eb347abbc1b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Activat de administrator"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Dezactivat de administrator"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Activată de Protecția avansată"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Dezactivată de Protecția avansată"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
index 59006449133d..a004a1fe60da 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Включено администратором"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Отключено администратором"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Включено Дополнительной защитой"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Отключено Дополнительной защитой"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
index de89710d7acd..addc8b3ae15c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"පරිපාලක විසින් සබල කර ඇත"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ඔබගේ පරිපාලක විසින් අබල කර ඇත"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"උසස් ආරක්ෂණය මගින් සබල කර ඇත"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"උසස් ආරක්ෂණය මගින් අබල කර ඇත"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
index b8bb91942799..0277696560b9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Povolené správcom"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Zakázané správcom"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivované rozšírenou ochranou"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Deaktivované rozšírenou ochranou"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
index 1d8ee573e233..eb886bcf1232 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Omogočil skrbnik"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Onemogočil skrbnik"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Omogočila dodatna zaščita"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Onemogočila dodatna zaščita"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
index 4ee40bf39b59..2de5ffe47c1c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktivizuar nga administratori"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Çaktivizuar nga administratori"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktivizuar nga \"Mbrojtja e përparuar\""</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Çaktivizuar nga \"Mbrojtja e përparuar\""</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
index 9d006a7ef4ef..94f52a0b95a2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Администратор је омогућио"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Администратор је онемогућио"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Омогућила је Напредна заштита"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Онемогућила је Напредна заштита"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
index faea0703468e..b41c4d8a8275 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Aktiverad av administratör"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Inaktiverad av administratören"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Aktiverades av Avancerat skydd"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Inaktiverades av Avancerat skydd"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
index 59f511aea6fc..4d2e0d994972 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Imewashwa na msimamizi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Imezimwa na msimamizi"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Imewashwa kwa kutumia Ulinzi wa Hali ya Juu"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Imezimwa kwa kutumia Ulinzi wa Hali ya Juu"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
index 3ef5f77e96dc..55b300657eaf 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"நிர்வாகி இயக்கியுள்ளார்"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"நிர்வாகி முடக்கியுள்ளார்"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"மேம்பட்ட பாதுகாப்பு அமைப்பால் இயக்கப்பட்டது"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"மேம்பட்ட பாதுகாப்பு அமைப்பால் முடக்கப்பட்டது"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
index 8f17dc5ec1e8..fc6d00b22d34 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"అడ్మిన్ ఎనేబుల్ చేశారు"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"అడ్మిన్ డిజేబుల్ చేశారు"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"అడ్వాన్స్‌డ్ ప్రొటెక్షన్ ద్వారా ఎనేబుల్ చేయబడింది"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"అడ్వాన్స్‌డ్ ప్రొటెక్షన్ ద్వారా డిజేబుల్ చేయబడింది"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
index 80fd0c04cb2f..51da8dec6443 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"เปิดใช้โดยผู้ดูแลระบบ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"ปิดใช้โดยผู้ดูแลระบบ"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"เปิดใช้โดยการปกป้องขั้นสูง"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"ปิดใช้โดยการปกป้องขั้นสูง"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
index a4a538dc84ba..7fbf0e7ace73 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Na-enable ng admin"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Na-disable ng admin"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Na-enable ng Advanced na Proteksyon"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Na-disable ng Advanced na Proteksyon"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
index ac5ed6a152b7..a529ca557ba1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Yönetici tarafından etkinleştirildi"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Yönetici tarafından devre dışı bırakıldı"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Gelişmiş Koruma tarafından etkinleştirildi"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Gelişmiş Koruma tarafından devre dışı bırakıldı"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
index 32f02a45608e..fdf4160b1bb0 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Увімкнено адміністратором"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Вимкнено адміністратором"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Увімкнено Додатковим захистом"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Вимкнено Додатковим захистом"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
index f3752d90497c..b40cb6827523 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"منتظم کی طرف سے فعال کردہ"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"منتظم کی طرف سے غیر فعال کردہ"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"اعلی تحفظ نے فعال کیا ہے"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"اعلی تحفظ نے غیر فعال کیا ہے"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
index e2e9f423f501..9a27735407e2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Administrator tomonidan yoqilgan"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Administrator tomonidan faolsizlantirilgan"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Kuchaytirilgan himoya tomonidan yoqilgan"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Kuchaytirilgan himoya tomonidan faolsizlantirilgan"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
index dd654b290977..3436762f8355 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Do quản trị viên bật"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Quản trị viên đã vô hiệu hóa chế độ này"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Được bật bởi chế độ Bảo vệ nâng cao"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Bị tắt bởi chế độ Bảo vệ nâng cao"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
index 8fa969ea5112..5c9e302f274c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已被管理员启用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已被管理员停用"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已被“高级保护”功能启用"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已被“高级保护”功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
index 501f86084340..d4b883355cf3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已由進階保護功能啟用"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已由進階保護功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
index 501f86084340..d4b883355cf3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"已由進階保護功能啟用"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"已由進階保護功能停用"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
index 86a6acb92df4..2a93d00d687f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
@@ -19,4 +19,6 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Kunikwe amandla umlawuli"</string>
<string name="disabled_by_admin" msgid="4023569940620832713">"Kukhutshazwe umlawuli"</string>
+ <string name="enabled_by_advanced_protection" msgid="6236917660829422499">"Kunikwe Amandla Ukuvikela Okuthuthukile"</string>
+ <string name="disabled_by_advanced_protection" msgid="369596009193239632">"Kukhutshazwe Ukuvikela Okuthuthukile"</string>
</resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
index 7e4460ba815d..75809730a514 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
@@ -17,9 +17,12 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Summary for switch preference to denote it is switched on [CHAR LIMIT=50] -->
+ <!-- Summary for switch preference to denote it is switched on by an admin [CHAR LIMIT=50] -->
<string name="enabled_by_admin">Enabled by admin</string>
- <!-- Summary for switch preference to denote it is switched off [CHAR LIMIT=50] -->
+ <!-- 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>
-
-</resources> \ No newline at end of file
+ <!-- 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/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index 0cd0b3cb14f1..19818e0b06da 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,7 @@
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
android:gravity="center"
- android:layout_marginEnd="-4dp"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall6"
android:filterTouchesWhenObscured="false">
<androidx.preference.internal.PreferenceImageView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index 944bef6c9e09..c837ff43e46b 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,7 +21,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
- android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
+ android:paddingStart="@dimen/settingslib_expressive_space_none"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small1"
android:filterTouchesWhenObscured="false">
<TextView
diff --git a/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml
new file mode 100644
index 000000000000..eb48b4ec2515
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Vou uit"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Vou in"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml
new file mode 100644
index 000000000000..442819244b25
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ዘርጋ"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ሰብስብ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml
new file mode 100644
index 000000000000..e6d4c7b6cd19
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"توسيع"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"تصغير"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml
new file mode 100644
index 000000000000..2b5a5c98b98c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"বিস্তাৰ কৰক"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"সংকোচন কৰক"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml
new file mode 100644
index 000000000000..c7adee3095f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Genişləndirin"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Yığcamlaşdırın"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 000000000000..8b245a63cd91
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skupi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml
new file mode 100644
index 000000000000..b468f819397a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Разгарнуць"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Згарнуць"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml
new file mode 100644
index 000000000000..b177fa76a7d9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Разгъване"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Свиване"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml
new file mode 100644
index 000000000000..67bb59fab828
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"বড় করুন"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"আড়াল করুন"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml
new file mode 100644
index 000000000000..31afc8b07002
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Suzi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml
new file mode 100644
index 000000000000..0f999c9a20e2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Desplega"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Replega"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml
new file mode 100644
index 000000000000..144dba82e50b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozbalit"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sbalit"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml
new file mode 100644
index 000000000000..85497f143ef9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Udvid"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skjul"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml
new file mode 100644
index 000000000000..9e47741a0945
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Maximieren"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Minimieren"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml
new file mode 100644
index 000000000000..0b325b5ac4cd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Ανάπτυξη"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Σύμπτυξη"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml
new file mode 100644
index 000000000000..2539aa040a60
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml
new file mode 100644
index 000000000000..2539aa040a60
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml
new file mode 100644
index 000000000000..2539aa040a60
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml
new file mode 100644
index 000000000000..c976a2ed7757
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expandir"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Contraer"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml
new file mode 100644
index 000000000000..72ba9d11ecf1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Mostrar"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ocultar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml
new file mode 100644
index 000000000000..856360669354
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Laienda"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ahenda"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml
new file mode 100644
index 000000000000..d8c2c35aed31
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Zabaldu"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Tolestu"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml
new file mode 100644
index 000000000000..24087411b5b9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ازهم بازکردن"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"جمع کردن"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml
new file mode 100644
index 000000000000..0d226bf8bbe6
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Laajenna"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Tiivistä"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml
new file mode 100644
index 000000000000..fecfaa275e5e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Développer"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Réduire"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml
new file mode 100644
index 000000000000..fecfaa275e5e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Développer"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Réduire"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml
new file mode 100644
index 000000000000..7999aa1b4658
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Despregar"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Contraer"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml
new file mode 100644
index 000000000000..1457d34d3691
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"મોટું કરો"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"નાનું કરો"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml
new file mode 100644
index 000000000000..856379aaea84
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"बड़ा करें"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"छोटा करें"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml
new file mode 100644
index 000000000000..2d637f4a94c0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sažmi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml
new file mode 100644
index 000000000000..42731635f35c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Kibontás"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Összecsukás"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml
new file mode 100644
index 000000000000..97c1d602b94a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Luaskan"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ciutkan"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml
new file mode 100644
index 000000000000..cc4d05bc869c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Stækka"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Minnka"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml
new file mode 100644
index 000000000000..8edcf2450b22
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Espandi"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Comprimi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml
new file mode 100644
index 000000000000..784bd8e34789
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"הרחבה"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"כיווץ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml
new file mode 100644
index 000000000000..ec8f1ddaad7d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"გაფართოება"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ჩაკეცვა"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml
new file mode 100644
index 000000000000..329ccbbcddde
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Жаю"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Жию"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml
new file mode 100644
index 000000000000..5ca0269e8eb8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ពង្រីក"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"បង្រួម"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml
new file mode 100644
index 000000000000..05d4921064cc
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"펼치기"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"접기"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml
new file mode 100644
index 000000000000..4f5b8dc3643d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Жайып көрсөтүү"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Жыйыштыруу"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml
new file mode 100644
index 000000000000..d6ec47939596
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ຂະຫຍາຍ"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ຫຍໍ້ລົງ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml
new file mode 100644
index 000000000000..54076c4fd3c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Išskleisti"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sutraukti"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml
new file mode 100644
index 000000000000..2f87b0eb7abe
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Izvērst"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sakļaut"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml
new file mode 100644
index 000000000000..b4f8fb901e26
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Прошири"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Собери"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml
new file mode 100644
index 000000000000..c68141e31d96
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"വികസിപ്പിക്കുക"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ചുരുക്കുക"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml
new file mode 100644
index 000000000000..86b333d115d0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Дэлгэх"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Хураах"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml
new file mode 100644
index 000000000000..1ffa5a083575
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Kembangkan"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Kuncupkan"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml
new file mode 100644
index 000000000000..6f79acc6e8ad
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ပိုပြပါ"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"လျှော့ပြပါ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml
new file mode 100644
index 000000000000..359c9abe316e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Vis"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skjul"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml
new file mode 100644
index 000000000000..374fd31129c9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"एक्स्पान्ड गर्नुहोस्"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"कोल्याप्स गर्नुहोस्"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml
new file mode 100644
index 000000000000..76a4f9996340
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Uitvouwen"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Samenvouwen"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml
new file mode 100644
index 000000000000..1e1e87025986
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ବିସ୍ତାର କରନ୍ତୁ"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml
new file mode 100644
index 000000000000..da273b3bd137
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozwiń"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Zwiń"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000000..4e3d0e624d16
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Abrir"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Fechar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml
new file mode 100644
index 000000000000..4e3d0e624d16
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Abrir"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Fechar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml
new file mode 100644
index 000000000000..ec208843efa4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Extinde"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Restrânge"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml
new file mode 100644
index 000000000000..ba6ab9687d41
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Развернуть"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Свернуть"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml
new file mode 100644
index 000000000000..9adb6466c9a4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"දිග හරින්න"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"හකුළන්න"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml
new file mode 100644
index 000000000000..574ee839a089
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozbaliť"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Zbaliť"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml
new file mode 100644
index 000000000000..e02ecbfada36
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Zgjero"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Palos"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml
new file mode 100644
index 000000000000..35f6aa3a635a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Прошири"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Скупи"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml
new file mode 100644
index 000000000000..241683984626
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Utöka"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Komprimera"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml
new file mode 100644
index 000000000000..9a6075842a22
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Panua"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Kunja"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml
new file mode 100644
index 000000000000..d6dce9c076c9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ขยาย"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ยุบ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml
new file mode 100644
index 000000000000..8c7dbcfd987a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Genişlet"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Daralt"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml
new file mode 100644
index 000000000000..6da0ca81450e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Розгорнути"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Згорнути"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml
new file mode 100644
index 000000000000..46f3351b7964
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Mở rộng"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Thu gọn"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000000..ea5f29b2413b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展开"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收起"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml
new file mode 100644
index 000000000000..36203139566c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展開"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收合"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000000..36203139566c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展開"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收合"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml
new file mode 100644
index 000000000000..725d8bc27b2f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Nweba"</string>
+ <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Goqa"</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 73d0beccd0ba..02e190417853 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.3"
+ extra["jetpackComposeVersion"] = "1.8.0-alpha06"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 272dc2d1958f..74811d3ae7a6 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.6.1"
+agp = "8.7.2"
compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip
index 45f0424639f2..f8c4ecb9df7a 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 1c25e97452eb..ca510ebb7271 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=gradle-8.10.2-bin.zip
+distributionUrl=gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 914f06c1fef7..1f32ad6622a2 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,14 +54,14 @@ android {
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.4.0-alpha01")
- api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
+ api("androidx.compose.material3:material3:1.4.0-alpha04")
+ api("androidx.compose.material:material-icons-extended")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.1")
+ api("androidx.navigation:navigation-compose:2.9.0-alpha03")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.12.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index 5baf7be98666..b5a6ffa03317 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -22,10 +22,14 @@ import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HE
import android.content.Context
import android.content.pm.UserInfo
import com.android.settingslib.R
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
interface IEnterpriseRepository {
fun getEnterpriseString(updatableStringId: String, resId: Int): String
+ fun getAdminSummaryString(advancedProtectionStringId: Int, updatableStringId: String,
+ resId: Int, enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?, userId: Int): String
}
class EnterpriseRepository(private val context: Context) : IEnterpriseRepository {
@@ -34,6 +38,21 @@ class EnterpriseRepository(private val context: Context) : IEnterpriseRepository
override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
checkNotNull(resources.getString(updatableStringId) { context.getString(resId) })
+ override fun getAdminSummaryString(
+ advancedProtectionStringId: Int,
+ updatableStringId: String,
+ resId: Int,
+ enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?,
+ userId: Int
+ ): String {
+ return if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
+ enforcedAdmin?.enforcedRestriction, userId)) {
+ context.getString(advancedProtectionStringId)
+ } else {
+ getEnterpriseString(updatableStringId, resId)
+ }
+ }
+
fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) {
getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
} else if (userInfo.isPrivateProfile) {
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 b6d92422c333..a140eb8424a8 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
@@ -37,21 +37,27 @@ interface BlockedByEcm : RestrictedMode {
fun showRestrictedSettingsDetails()
}
-
internal data class BlockedByAdminImpl(
private val context: Context,
private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin,
+ private val userId: Int,
private val enterpriseRepository: IEnterpriseRepository = EnterpriseRepository(context),
) : BlockedByAdmin {
override fun getSummary(checked: Boolean?) = when (checked) {
- true -> enterpriseRepository.getEnterpriseString(
+ true -> enterpriseRepository.getAdminSummaryString(
+ advancedProtectionStringId = R.string.enabled_by_advanced_protection,
updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
resId = R.string.enabled_by_admin,
+ enforcedAdmin = enforcedAdmin,
+ userId = userId,
)
- false -> enterpriseRepository.getEnterpriseString(
+ false -> enterpriseRepository.getAdminSummaryString(
+ advancedProtectionStringId = R.string.disabled_by_advanced_protection,
updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
resId = R.string.disabled_by_admin,
+ enforcedAdmin = enforcedAdmin,
+ userId = userId,
)
else -> ""
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 6b1893c73b3f..3309faaa8db2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -84,7 +84,11 @@ internal class RestrictionsProviderImpl(
for (key in restrictions.keys) {
RestrictedLockUtilsInternal
.checkIfRestrictionEnforced(context, key, restrictions.userId)
- ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
+ ?.let { return BlockedByAdminImpl(
+ context = context,
+ enforcedAdmin = it,
+ userId = restrictions.userId
+ ) }
}
restrictions.enhancedConfirmation?.let { ec ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index d89d3977cac3..5a524d944a24 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -133,7 +133,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) {
list.joinToString(separator = System.lineSeparator())
}
if (footer.isBlank()) return
- HorizontalDivider()
+ if (!isSpaExpressiveEnabled) HorizontalDivider()
Column(
modifier =
if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
index 8fd16b37bfeb..f3245c9085e7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
@@ -16,19 +16,49 @@
package com.android.settingslib.spaprivileged.model.enterprise
+import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.app.admin.EnforcingAdmin
import android.content.Context
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.security.Flags
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminAdvancedProtection
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminNotAdvancedProtection
+import com.android.settingslib.widget.restricted.R
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class RestrictedModeTest {
+ @Rule
+ @JvmField
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
private val context: Context = ApplicationProvider.getApplicationContext()
+ @Mock
+ private lateinit var devicePolicyManager: DevicePolicyManager
+
private val fakeEnterpriseRepository = object : IEnterpriseRepository {
override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
when (updatableStringId) {
@@ -36,20 +66,123 @@ class RestrictedModeTest {
Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY -> DISABLED_BY_ADMIN
else -> ""
}
+
+ override fun getAdminSummaryString(
+ advancedProtectionStringId: Int,
+ updatableStringId: String,
+ resId: Int,
+ enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?,
+ userId: Int
+ ): String {
+ 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
+ else -> ""
+ }
+ }
+ return getEnterpriseString(updatableStringId, resId)
+ }
}
+ @Before
+ fun setUp() {
+ whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
@Test
fun blockedByAdmin_getSummaryWhenChecked() {
- val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+ val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, USER_ID,
+ fakeEnterpriseRepository)
val summary = blockedByAdmin.getSummary(true)
assertThat(summary).isEqualTo(ENABLED_BY_ADMIN)
}
+ @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
@Test
fun blockedByAdmin_getSummaryNotWhenChecked() {
- val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+ val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, USER_ID,
+ fakeEnterpriseRepository)
+
+ val summary = blockedByAdmin.getSummary(false)
+
+ assertThat(summary).isEqualTo(DISABLED_BY_ADMIN)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun blockedByAdmin_disabledByAdvancedProtection_getSummaryWhenChecked() {
+ val blockedByAdmin =
+ BlockedByAdminImpl(
+ context = context,
+ enforcedAdmin = ENFORCED_ADMIN,
+ enterpriseRepository = fakeEnterpriseRepository,
+ userId = USER_ID,
+ )
+
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+ val summary = blockedByAdmin.getSummary(true)
+
+ assertThat(summary).isEqualTo(ENABLED_BY_ADVANCED_PROTECTION)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun blockedByAdmin_disabledByAdvancedProtection_getSummaryWhenNotChecked() {
+ val blockedByAdmin =
+ BlockedByAdminImpl(
+ context = context,
+ enforcedAdmin = ENFORCED_ADMIN,
+ enterpriseRepository = fakeEnterpriseRepository,
+ userId = USER_ID,
+ )
+
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+ val summary = blockedByAdmin.getSummary(false)
+
+ assertThat(summary).isEqualTo(DISABLED_BY_ADVANCED_PROTECTION)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun blockedByAdmin_notDisabledByAdvancedProtection_getSummaryWhenChecked() {
+ val blockedByAdmin =
+ BlockedByAdminImpl(
+ context = context,
+ enforcedAdmin = ENFORCED_ADMIN,
+ enterpriseRepository = fakeEnterpriseRepository,
+ userId = USER_ID,
+ )
+
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
+
+ val summary = blockedByAdmin.getSummary(true)
+
+ assertThat(summary).isEqualTo(ENABLED_BY_ADMIN)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun blockedByAdmin_notDisabledByAdvancedProtection_getSummaryWhenNotChecked() {
+ val blockedByAdmin =
+ BlockedByAdminImpl(
+ context = context,
+ enforcedAdmin = ENFORCED_ADMIN,
+ enterpriseRepository = fakeEnterpriseRepository,
+ userId = USER_ID,
+ )
+
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
val summary = blockedByAdmin.getSummary(false)
@@ -57,11 +190,19 @@ class RestrictedModeTest {
}
private companion object {
+ const val PACKAGE_NAME = "package.name"
const val RESTRICTION = "restriction"
+ const val USER_ID = 0
val ENFORCED_ADMIN: RestrictedLockUtils.EnforcedAdmin =
RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+ val ENFORCING_ADMIN_ADVANCED_PROTECTION: EnforcingAdmin =
+ getEnforcingAdminAdvancedProtection(PACKAGE_NAME, USER_ID)
+ val ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION: EnforcingAdmin =
+ getEnforcingAdminNotAdvancedProtection(PACKAGE_NAME, USER_ID)
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"
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index e73611510f6b..79085af63c6d 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -16,8 +16,17 @@
package com.android.settingslib.spaprivileged.template.app
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.app.admin.DevicePolicyResourcesManager
+import android.app.admin.EnforcingAdmin
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.security.Flags
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
@@ -29,28 +38,59 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdminImpl
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminAdvancedProtection
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminNotAdvancedProtection
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class TogglePermissionAppListPageTest {
+ @Rule
+ @JvmField
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@get:Rule
val composeTestRule = createComposeRule()
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var devicePolicyManager: DevicePolicyManager
+
+ @Mock
+ private lateinit var devicePolicyResourcesManager: DevicePolicyResourcesManager
+
+ @Spy
private val context: Context = ApplicationProvider.getApplicationContext()
private val fakeNavControllerWrapper = FakeNavControllerWrapper()
private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+ @Before
+ fun setUp() {
+ whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+ whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+ }
+
@Test
fun pageTitle() {
val listModel = TestTogglePermissionAppListModel()
@@ -98,10 +138,65 @@ class TogglePermissionAppListPageTest {
assertThat(summary).isEqualTo(context.getPlaceholder())
}
+ @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
@Test
fun summary_whenAllowedButAdminOverrideToNotAllowed() {
fakeRestrictionsProvider.restrictedMode =
- BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN)
+ BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
+ val listModel =
+ TestTogglePermissionAppListModel(
+ isAllowed = true,
+ switchifBlockedByAdminOverrideCheckedValueTo = false,
+ )
+
+ val summary = getSummary(listModel)
+
+ assertThat(summary)
+ .isEqualTo(
+ context.getString(
+ com.android.settingslib.widget.restricted.R.string.disabled_by_admin
+ )
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun summary_disabledByAdvancedProtection_whenAllowedButAdminOverrideToNotAllowed() {
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+ fakeRestrictionsProvider.restrictedMode =
+ BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
+ val listModel =
+ TestTogglePermissionAppListModel(
+ isAllowed = true,
+ switchifBlockedByAdminOverrideCheckedValueTo = false,
+ )
+
+ val summary = getSummary(listModel)
+
+ assertThat(summary)
+ .isEqualTo(
+ context.getString(
+ com.android.settingslib.widget.restricted.R.string
+ .disabled_by_advanced_protection
+ )
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+ @Test
+ fun summary_notDisabledByAdvancedProtection_whenAllowedButAdminOverrideToNotAllowed() {
+ val disabledByAdminText = context.getString(
+ com.android.settingslib.widget.restricted.R.string.disabled_by_admin
+ )
+ whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+ .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
+ whenever(devicePolicyResourcesManager.getString(
+ eq(Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY), any())).thenReturn(disabledByAdminText)
+
+ fakeRestrictionsProvider.restrictedMode =
+ BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
val listModel =
TestTogglePermissionAppListModel(
isAllowed = true,
@@ -186,7 +281,12 @@ class TogglePermissionAppListPageTest {
const val SUMMARY = "Summary"
val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME }
const val RESTRICTION = "restriction"
+ const val USER_ID = 0
val ENFORCED_ADMIN: RestrictedLockUtils.EnforcedAdmin =
RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+ val ENFORCING_ADMIN_ADVANCED_PROTECTION: EnforcingAdmin =
+ getEnforcingAdminAdvancedProtection(PACKAGE_NAME, USER_ID)
+ val ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION: EnforcingAdmin =
+ getEnforcingAdminNotAdvancedProtection(PACKAGE_NAME, USER_ID)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
index f8ca2a084f14..d5e8d6a5fa13 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -16,6 +16,10 @@
package com.android.settingslib.spaprivileged.tests.testutils
+import android.app.admin.EnforcingAdmin
+import android.app.admin.UnknownAuthority
+import android.os.UserHandle
+import android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
@@ -55,3 +59,10 @@ class FakeRestrictionsProvider : RestrictionsProvider {
@Composable
override fun restrictedModeState() = stateOf(restrictedMode)
}
+
+fun getEnforcingAdminAdvancedProtection(packageName: String, userId: Int): EnforcingAdmin =
+ EnforcingAdmin(packageName, UnknownAuthority(ADVANCED_PROTECTION_SYSTEM_ENTITY),
+ UserHandle.of(userId))
+
+fun getEnforcingAdminNotAdvancedProtection(packageName: String, userId: Int): EnforcingAdmin =
+ EnforcingAdmin(packageName, UnknownAuthority.UNKNOWN_AUTHORITY, UserHandle.of(userId))
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 9764e64b8509..4428480eaa3e 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -75,6 +75,7 @@ open class TopIntroPreference @JvmOverloads constructor(
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
+ visibility = if (title.isNullOrEmpty()) View.GONE else View.VISIBLE
setText(title.toString())
if (hyperlinkListener != null) {
setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 0a83aabfce7b..bf4cb9914fdb 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -20,6 +20,7 @@ android_library {
static_libs: [
"androidx.preference_preference",
"androidx-constraintlayout_constraintlayout",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 0e71a1b127e7..4c45c5372a8a 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -42,7 +42,7 @@ import java.util.regex.Pattern;
*
* <p>This preference shows number in usage summary with enlarged font size.
*/
-public class UsageProgressBarPreference extends Preference {
+public class UsageProgressBarPreference extends Preference implements GroupSectionDividerMixin {
private final Pattern mNumberPattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+");
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 89de995fa5ef..1a043d5015b2 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -98,6 +98,7 @@ flag {
namespace: "android_settings"
description: "Settings catalyst project migration"
bug: "323791114"
+ is_exported: true
}
flag {
@@ -106,6 +107,7 @@ flag {
namespace: "android_settings"
description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
bug: "375193223"
+ is_exported: true
}
flag {
@@ -183,5 +185,15 @@ flag {
name: "hearing_device_set_connection_status_report"
namespace: "accessibility"
description: "Enable the connection status report for a set of hearing device."
- bug: "357878944"
+ bug: "357882387"
+}
+
+flag {
+ name: "ignore_a2dp_disconnection_for_android_auto"
+ namespace: "cross_device_experiences"
+ description: "Do not show problem connecting message when Android Auto disconnect A2DP"
+ bug: "381981752"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
new file mode 100644
index 000000000000..2c1f300f22ee
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:maxLevel="0" android:drawable="@drawable/ic_ambient_volume_0_0" />
+ <item android:maxLevel="1" android:drawable="@drawable/ic_ambient_volume_0_1" />
+ <item android:maxLevel="2" android:drawable="@drawable/ic_ambient_volume_0_2" />
+ <item android:maxLevel="3" android:drawable="@drawable/ic_ambient_volume_0_3" />
+ <item android:maxLevel="4" android:drawable="@drawable/ic_ambient_volume_0_4" />
+ <item android:maxLevel="5" android:drawable="@drawable/ic_ambient_volume_1_0" />
+ <item android:maxLevel="6" android:drawable="@drawable/ic_ambient_volume_1_1" />
+ <item android:maxLevel="7" android:drawable="@drawable/ic_ambient_volume_1_2" />
+ <item android:maxLevel="8" android:drawable="@drawable/ic_ambient_volume_1_3" />
+ <item android:maxLevel="9" android:drawable="@drawable/ic_ambient_volume_1_4" />
+ <item android:maxLevel="10" android:drawable="@drawable/ic_ambient_volume_2_0" />
+ <item android:maxLevel="11" android:drawable="@drawable/ic_ambient_volume_2_1" />
+ <item android:maxLevel="12" android:drawable="@drawable/ic_ambient_volume_2_2" />
+ <item android:maxLevel="13" android:drawable="@drawable/ic_ambient_volume_2_3" />
+ <item android:maxLevel="14" android:drawable="@drawable/ic_ambient_volume_2_4" />
+ <item android:maxLevel="15" android:drawable="@drawable/ic_ambient_volume_3_0" />
+ <item android:maxLevel="16" android:drawable="@drawable/ic_ambient_volume_3_1" />
+ <item android:maxLevel="17" android:drawable="@drawable/ic_ambient_volume_3_2" />
+ <item android:maxLevel="18" android:drawable="@drawable/ic_ambient_volume_3_3" />
+ <item android:maxLevel="19" android:drawable="@drawable/ic_ambient_volume_3_4" />
+ <item android:maxLevel="20" android:drawable="@drawable/ic_ambient_volume_4_0" />
+ <item android:maxLevel="21" android:drawable="@drawable/ic_ambient_volume_4_1" />
+ <item android:maxLevel="22" android:drawable="@drawable/ic_ambient_volume_4_2" />
+ <item android:maxLevel="23" android:drawable="@drawable/ic_ambient_volume_4_3" />
+ <item android:maxLevel="24" android:drawable="@drawable/ic_ambient_volume_4_4" />
+</level-list> \ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
new file mode 100644
index 000000000000..738fe89636c9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M792,904L671,783Q646,799 618,810.5Q590,822 560,829L560,747Q574,742 587.5,737Q601,732 613,725L480,592L480,800L280,600L120,600L120,360L248,360L56,168L112,112L848,848L792,904ZM784,672L726,614Q743,583 751.5,549Q760,515 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,532 825.5,581Q811,630 784,672ZM650,538L560,448L560,318Q607,340 633.5,384Q660,428 660,480Q660,495 657.5,509.5Q655,524 650,538ZM480,368L376,264L480,160L480,368ZM400,606L400,512L328,440L328,440L200,440L200,520L314,520L400,606ZM364,476L364,476L364,476L364,476L364,476L364,476L364,476Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
new file mode 100644
index 000000000000..8de24f980927
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
new file mode 100644
index 000000000000..267093ecc9eb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
new file mode 100644
index 000000000000..fb21254faf76
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
new file mode 100644
index 000000000000..4f1e054b0f97
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
new file mode 100644
index 000000000000..d151fee3e8ab
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
new file mode 100644
index 000000000000..3e97a2e757f9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
new file mode 100644
index 000000000000..7bfd662c8261
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
new file mode 100644
index 000000000000..bba33b348749
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
new file mode 100644
index 000000000000..c0043166a121
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
@@ -0,0 +1,43 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
new file mode 100644
index 000000000000..d89ed87f7e40
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
new file mode 100644
index 000000000000..e0a9c410e74b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
new file mode 100644
index 000000000000..8627af9ab88f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
new file mode 100644
index 000000000000..2c1139a3a042
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
@@ -0,0 +1,43 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
new file mode 100644
index 000000000000..8d920b182286
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
@@ -0,0 +1,47 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
new file mode 100644
index 000000000000..7fd4e17e51ca
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.043,0.761L8.814,0.286L9.887,4.192L8.116,4.666L7.043,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.02,3.652L4.317,2.355L7.162,5.201L5.866,6.497L3.02,3.652Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.898,8.942L1.153,7.127L5.163,7.636L4.908,9.451L0.898,8.942Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.589,13.874L1.053,12.121L4.93,10.941L5.466,12.694L1.589,13.874Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
new file mode 100644
index 000000000000..f0f81506f446
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
new file mode 100644
index 000000000000..1bb20170bc9b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
@@ -0,0 +1,43 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
new file mode 100644
index 000000000000..a8bc0af46ccc
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
@@ -0,0 +1,47 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
new file mode 100644
index 000000000000..f8a58323e6ba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
@@ -0,0 +1,51 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.162,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
new file mode 100644
index 000000000000..e5d7ad420441
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
new file mode 100644
index 000000000000..f5cdf5d62db4
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
new file mode 100644
index 000000000000..cbed634e4544
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
new file mode 100644
index 000000000000..90d81d8bd9ec
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.566,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
new file mode 100644
index 000000000000..f1a9a8a942b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
@@ -0,0 +1,43 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.975,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.975,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.878,8.868L1.134,7.052L5.163,7.632L4.907,9.447L0.878,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z" android:strokeAlpha="0.4"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 322598574f26..ae04ca158e01 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -366,10 +366,10 @@
<string name="debug_app" msgid="8903350241392391766">"Kies ontfoutprogram"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"Geen ontfoutprogram gestel nie"</string>
<string name="debug_app_set" msgid="6599535090477753651">"Ontfoutingsprogram: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="select_application" msgid="2543228890535466325">"Kies program"</string>
+ <string name="select_application" msgid="2543228890535466325">"Kies app"</string>
<string name="no_application" msgid="9038334538870247690">"Niks"</string>
<string name="wait_for_debugger" msgid="7461199843335409809">"Wag vir ontfouter"</string>
- <string name="wait_for_debugger_summary" msgid="6846330006113363286">"Ontfoutde program wag vir ontfouter om te heg voordat dit uitgevoer word"</string>
+ <string name="wait_for_debugger_summary" msgid="6846330006113363286">"Ontfoute app wag vir ontfouter om te heg voordat dit uitgevoer word"</string>
<string name="debug_input_category" msgid="7349460906970849771">"Invoer"</string>
<string name="debug_drawing_category" msgid="5066171112313666619">"Skets"</string>
<string name="debug_hw_drawing_category" msgid="5830815169336975162">"Hardeware-versnelde lewering"</string>
@@ -428,11 +428,11 @@
<string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Vernietig elke aktiwiteit sodra die gebruiker dit verlaat"</string>
<string name="app_process_limit_title" msgid="8361367869453043007">"Agtergrondproseslimiet"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"Wys agtergrond-ANR\'e"</string>
- <string name="show_all_anrs_summary" msgid="8562788834431971392">"Wys Program Reageer Nie-dialoog vir agtergrondprogramme"</string>
+ <string name="show_all_anrs_summary" msgid="8562788834431971392">"Wys App Reageer Nie-dialoog vir agtergrondapps"</string>
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"Wys kennisgewingkanaalwaarskuwings"</string>
- <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Wys waarskuwing op skerm wanneer \'n program \'n kennisgewing sonder \'n geldige kanaal plaas"</string>
+ <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Wys waarskuwing op skerm wanneer \'n app \'n kennisgewing sonder \'n geldige kanaal plaas"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Dwing toelating op eksterne berging"</string>
- <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Maak dat enige program na eksterne berging geskryf kan word, ongeag manifeswaardes"</string>
+ <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Maak dat enige app na eksterne berging geskryf kan word, ongeag manifeswaardes"</string>
<string name="force_resizable_activities" msgid="7143612144399959606">"Dwing aktiwiteite om verstelbaar te wees"</string>
<string name="force_resizable_activities_summary" msgid="2490382056981583062">"Maak die groottes van alle aktiwiteite verstelbaar vir veelvuldige vensters, ongeag manifeswaardes."</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"Aktiveer vormvrye vensters"</string>
@@ -546,7 +546,7 @@
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Aktiewe invoermetodes"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Gebruik stelseltale"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Kon nie instellings vir <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> oopmaak nie"</string>
- <string name="ime_security_warning" msgid="6547562217880551450">"Die invoermetode kan dalk alle teks wat jy invoer, versamel, insluitend persoonlike data soos wagwoorde en kredietkaartnommers. Dit kom van die program <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Wil jy dié invoermetode gebruik?"</string>
+ <string name="ime_security_warning" msgid="6547562217880551450">"Die invoermetode kan dalk alle teks wat jy invoer, versamel, insluitend persoonlike data soos wagwoorde en kredietkaartnommers. Dit kom van die app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Wil jy dié invoermetode gebruik?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Let wel: Ná \'n herselflaai kan hierdie app nie begin voordat jy jou foon ontsluit het nie"</string>
<string name="ims_reg_title" msgid="8197592958123671062">"IMS-registrasiestaat"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"Geregistreer"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Hierdie foon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Hierdie tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Hierdie rekenaar (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Hierdie TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokluidspreker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Eksterne toestel"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Gekoppelde toestel"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Gekoppel deur ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Gekoppel deur eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV-verstek"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-uitset"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne luidsprekers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ingeboude luidspreker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-oudio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Kan nie koppel nie. Skakel toestel af en weer aan"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Bedrade oudiotoestel"</string>
<string name="help_label" msgid="3528360748637781274">"Hulp en terugvoer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 34f37b47fae6..03cde01a722d 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ይህ ስልክ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ይህ ጡባዊ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ይህ ኮምፒውተር (ውስጣዊ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ይህ ቲቪ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"የመትከያ ድምፅ ማውጫ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"የውጭ መሣሪያ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"የተገናኘ መሣሪያ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI ኢኤአርሲ"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"በኤአርሲ በኩል ተገናኝቷል"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"በኢኤአርሲ በኩል ተገናኝቷል"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"የቲቪ ነባሪ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"የHDMI ውጤት"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ውስጣዊ ድምፅ ማውጫዎች"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"አብሮ የተሰራ ድምፅ ማውጫ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"የTV ኦዲዮ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"መገናኘት ላይ ችግር። መሳሪያውን ያጥፉት እና እንደገና ያብሩት"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ባለገመድ የኦዲዮ መሣሪያ"</string>
<string name="help_label" msgid="3528360748637781274">"እገዛ እና ግብረመልስ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 2ea1486ace31..87a82414195c 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"هذا الهاتف"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"هذا الجهاز اللوحي"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"هذا الكمبيوتر (داخلي)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"جهاز التلفزيون هذا"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"مكبّر صوت بقاعدة إرساء"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"جهاز خارجي"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"جهاز متّصل"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏متّصل من خلال ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏متّصل من خلال eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"الجهاز التلقائي لإخراج صوت التلفزيون"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏إخراج الصوت من خلال HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"مكبّرات الصوت الداخلية"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"مكبِّر الصوت المُدمَج"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"صوت التلفزيون"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"حدثت مشكلة أثناء الاتصال. يُرجى إيقاف الجهاز ثم إعادة تشغيله."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"جهاز سماعي سلكي"</string>
<string name="help_label" msgid="3528360748637781274">"المساعدة والملاحظات"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 432d443a5045..4aab7d63d4d0 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"এই ফ’নটো"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"এই টেবলেটটো"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"এই কম্পিউটাৰ (অভ্যন্তৰীণ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"এই টিভিটো"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ড’ক স্পীকাৰ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"বাহ্যিক ডিভাইচ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"সংযোগ হৈ থকা ডিভাইচ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARCৰ জৰিয়তে সংযুক্ত"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARCৰ জৰিয়তে সংযুক্ত"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"টিভি ডিফ’ল্ট"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI আউটপুট"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"অভ্যন্তৰীণ স্পীকাৰ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"বিল্ট-ইন স্পীকাৰ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"টিভিৰ অডিঅ’"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"সংযোগ হোৱাত সমস্যা হৈছে। ডিভাইচটো অফ কৰি পুনৰ অন কৰক"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"তাঁৰযুক্ত অডিঅ’ ডিভাইচ"</string>
<string name="help_label" msgid="3528360748637781274">"সহায় আৰু মতামত"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 330532ec48b0..5841bdab9310 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu planşet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompüter (daxili)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Bu TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dok dinamiki"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Xarici cihaz"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Qoşulmuş cihaz"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Chrome-da Tətbiqin İşləmə Müddəti vasitəsilə qoşulub"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC vasitəsilə qoşulub"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV defoltu"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI çıxışı"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Daxili dinamiklər"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Daxili dinamik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audiosu"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Qoşulmaqla bağlı problem. Cihazı deaktiv edin, sonra yenidən aktiv edin"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Simli audio cihaz"</string>
<string name="help_label" msgid="3528360748637781274">"Yardım və rəy"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 4dfd25302708..d36a8641da41 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovaj računar (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik bazne stanice"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Spoljni uređaj"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano preko ARC-a"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano preko eARC-a"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Podrazumevana vrednost za TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Unutrašnji zvučnici"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk TV-a"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem pri povezivanju. Isključite uređaj, pa ga ponovo uključite"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audio uređaj"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index de25460e4cbc..29f5542a7953 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Гэты тэлефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Гэты планшэт"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Гэты камп’ютар (унутраны)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Гэты тэлевізар"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Дынамік док-станцыі"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Знешняя прылада"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Падключаная прылада"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Падключана праз ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Падключана праз eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартны аўдыявыхад тэлевізара"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Выхад HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Унутраныя дынамікі"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Убудаваны дынамік"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аўдыя тэлевізара"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Праблема з падключэннем. Выключыце і зноў уключыце прыладу"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Правадная аўдыяпрылада"</string>
<string name="help_label" msgid="3528360748637781274">"Даведка і водгукі"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 47ce37cfc900..8d261a243614 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Този телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Този таблет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Този компютър (вграден)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Този телевизор"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Високоговорител докинг станция"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Външно устройство"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Свързано устройство"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Свързано посредством ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Свързано посредством eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартното за телевизора"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI изход"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Вградени високоговорители"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вграден високоговорител"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудио на телевизора"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"При свързването възникна проблем. Изключете устройството и го включете отново"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Аудиоустройство с кабел"</string>
<string name="help_label" msgid="3528360748637781274">"Помощ и отзиви"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 3b77cfdae6b5..0ad50cc04b01 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"এই ফোন"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"এই ট্যাবলেট"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"এই কম্পিউটার (ইন্টার্নাল)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"এই টিভি"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ডক স্পিকার"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"এক্সটার্নাল ডিভাইস"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"কানেক্ট থাকা ডিভাইস"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC-এর মাধ্যমে কানেক্ট করা হয়েছে"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC-এর মাধ্যমে কানেক্ট করা হয়েছে"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"টিভির ডিফল্ট সেটিং"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI আউটপুট"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ইন্টার্নাল স্পিকার"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"বিল্ট-ইন স্পিকার"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"টিভি অডিও"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"কানেক্ট করতে সমস্যা হচ্ছে। ডিভাইস বন্ধ করে আবার চালু করুন"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ওয়্যার অডিও ডিভাইস"</string>
<string name="help_label" msgid="3528360748637781274">"সহায়তা ও মতামত"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 65eb6817f631..a30bcfa406f1 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovaj računar (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik priključne stanice"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Vanjski uređaj"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano je putem ARC-a"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano je putem eARC-a"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Zadano za TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interni zvučnici"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk TV-a"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Došlo je do problema prilikom povezivanja. Isključite, pa ponovo uključite uređaj"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audio uređaj"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a7c3fafd48ef..ba733de812cb 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Aquest telèfon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Aquesta tauleta"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Aquest ordinador (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Aquest televisor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Base d\'altaveu"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositiu extern"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositiu connectat"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connectat mitjançant ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connectat mitjançant eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Televisor predeterminat"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortida HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altaveus interns"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altaveu integrat"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Àudio de la televisió"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Hi ha hagut un problema amb la connexió. Apaga el dispositiu i torna\'l a encendre."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositiu d\'àudio amb cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda i suggeriments"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 832d320cd805..1a239a119224 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interní)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tato televize"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Reproduktor doku"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externí zařízení"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Připojené zařízení"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Připojeno přes ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Připojeno přes eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Výchozí nastavení televize"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Výstup HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interní reproduktory"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vestavěný reproduktor"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk televize"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problém s připojením. Vypněte zařízení a znovu jej zapněte"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kabelové audiozařízení"</string>
<string name="help_label" msgid="3528360748637781274">"Nápověda a zpětná vazba"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 708d5df4c6b3..3753e033442f 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Denne tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne computer (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Dette fjernsyn"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockhøjttaler"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ekstern enhed"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Forbundet enhed"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Forbundet via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Forbundet via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardindstillinger for fjernsyn"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-udgang"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne højttalere"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Indbygget højttaler"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-lyd"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Der kunne ikke oprettes forbindelse. Sluk og tænd enheden"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhed med ledning"</string>
<string name="help_label" msgid="3528360748637781274">"Hjælp og feedback"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 069201a63174..7d0b848941d6 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Dieses Smartphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dieses Tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Dieser Computer (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Dieser Fernseher"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock-Lautsprecher"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externes Gerät"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Verbundenes Gerät"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Per ARC verbunden"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Per eARC verbunden"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardeinstellung: Fernseher"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-Ausgang"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne Lautsprecher"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Integrierter Lautsprecher"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV‑Audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus und wieder ein."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Netzbetriebenes Audiogerät"</string>
<string name="help_label" msgid="3528360748637781274">"Hilfe und Feedback"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 8577a9a8acdf..915cabbf1e0b 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Αυτό το τηλέφωνο"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Αυτό το tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Αυτός ο υπολογιστής (εσωτερ.)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Αυτή η τηλεόραση"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Ηχείο βάσης σύνδεσης"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Εξωτερική συσκευή"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Συνδεδεμένη συσκευή"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Συνδέθηκε μέσω ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Συνδέθηκε μέσω eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Προεπιλογή τηλεόρασης"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Έξοδος HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Εσωτερικά ηχεία"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ενσωματωμένο ηχείο"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Ήχος τηλεόρασης"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Πρόβλημα κατά τη σύνδεση. Απενεργοποιήστε τη συσκευή και ενεργοποιήστε την ξανά"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Ενσύρματη συσκευή ήχου"</string>
<string name="help_label" msgid="3528360748637781274">"Βοήθεια και σχόλια"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index f9d2f082b06c..d9c35f50582b 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 2cf98ef44c26..887841cafbc0 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"External Device"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off &amp; back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index f9d2f082b06c..d9c35f50582b 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index f9d2f082b06c..d9c35f50582b 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index a4834d887140..191a8eb9bb02 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Esta computadora (interna)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Bocina de la estación de carga"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Se estableció conexión con un cable ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Se estableció conexión con un cable eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Configuración predeterminada de la TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Salida de HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Bocinas internas"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Bocina integrada"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio de la TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Error al establecer la conexión. Apaga el dispositivo y vuelve a encenderlo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 1792fd13a5f5..95ebbfa74986 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta televisión"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altavoz de la base"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado mediante ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado mediante eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predeterminada de la televisión"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Salida HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altavoces internos"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altavoz integrado"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio de la televisión"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"No se ha podido conectar; reinicia el dispositivo"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 5411d0bd30b5..802c1e258e85 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"See telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"See tahvelarvuti"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"See arvuti (sisemine)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"See teler"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doki kõlar"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Väline seade"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ühendatud seade"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ühendatud ARC-i kaudu"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ühendatud eARC-i kaudu"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Teleri vaikeväljund"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-väljund"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisemised kõlarid"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Sisseehitatud kõlar"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Teleri heli"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Probleem ühendamisel. Lülitage seade välja ja uuesti sisse"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Juhtmega heliseade"</string>
<string name="help_label" msgid="3528360748637781274">"Abi ja tagasiside"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 090d99d3b771..16dd1f6ffba9 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -562,9 +562,9 @@
<string name="save" msgid="3745809743277153149">"Gorde"</string>
<string name="okay" msgid="949938843324579502">"Ados"</string>
<string name="done" msgid="381184316122520313">"Eginda"</string>
- <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarmak eta abisuak"</string>
- <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Eman alarmak eta abisuak ezartzeko baimena"</string>
- <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarmak eta abisuak"</string>
+ <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarmak eta gogorarazpenak"</string>
+ <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Eman alarmak eta gogorarazpenak ezartzeko baimena"</string>
+ <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarmak eta gogorarazpenak"</string>
<string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Eman alarmak ezartzeko eta denbora-muga duten ekintzak programatzeko baimena aplikazioari. Hala, aplikazioak atzeko planoan funtzionatuko du, eta litekeena da bateria gehiago kontsumitzea.\n\nBaimen hori ematen ez baduzu, ez dute funtzionatuko aplikazio honen bidez programatutako alarmek eta denbora-muga duten ekintzek."</string>
<string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"programazioa, alarma, gogorarazpena, erlojua"</string>
<string name="zen_mode_do_not_disturb_name" msgid="6798711401734798283">"Ez molestatzeko"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Telefono hau"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tableta hau"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ordenagailu hau (barnekoa)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Telebista hau"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Oinarri bozgorailuduna"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Kanpoko gailua"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Konektatutako gailua"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC bidez konektatuta"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC bidez konektatuta"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Telebistaren audio-erreproduzigailu lehenetsia"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI irteera"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Barneko bozgorailuak"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Bozgorailu integratua"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Telebistako audioa"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Arazo bat izan da konektatzean. Itzali gailua eta pitz ezazu berriro."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio-gailu kableduna"</string>
<string name="help_label" msgid="3528360748637781274">"Laguntza eta iritziak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index c350175b788d..85ef0f9cb5fd 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"این تلفن"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"این رایانه لوحی"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"این رایانه (داخلی)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"این تلویزیون"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"بلندگوی پایه اتصال"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"دستگاه خارجی"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"دستگاه متصل"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏متصل ازطریق ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏متصل ازطریق eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"پیش‌فرض تلویزیون"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏خروجی HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"بلندگوهای داخلی"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"بلندگوی داخلی"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"صدای تلویزیون"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"مشکل در اتصال. دستگاه را خاموش و دوباره روشن کنید"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"دستگاه صوتی سیمی"</string>
<string name="help_label" msgid="3528360748637781274">"راهنما و بازخورد"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 18a84b5f4ead..4b18d2f96515 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tämä puhelin"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tämä tabletti"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tämä tietokone (sisäinen)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tämä TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Telinekaiutin"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ulkoinen laite"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Yhdistetty laite"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Yhdistetty ARC:n kautta"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Yhdistetty eARC:n kautta"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV:n oletusasetus"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-toisto"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisäiset kaiuttimet"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Sisäänrakennettu kaiutin"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV:n audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Yhteysvirhe. Sammuta laite ja käynnistä se uudelleen."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Langallinen äänilaite"</string>
<string name="help_label" msgid="3528360748637781274">"Ohjeet ja palaute"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 066244a06620..a1d15997b630 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ce téléviseur"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Haut-parleur du socle"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Appareil externe"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Appareil connecté"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connecté par ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connecté par eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sortie audio par défaut de la télévision"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortie HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Haut-parleurs internes"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Haut-parleur intégré"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sortie audio du téléviseur"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteignez et rallumez l\'appareil"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio à câble"</string>
<string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 84c2f15b12e2..f22f8bffcc41 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ce téléviseur"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Haut-parleur station d\'accueil"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Appareil externe"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Appareil connecté"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connecté via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connecté via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sortie par défaut de la TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortie HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Haut-parleurs internes"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Haut-parleur intégré"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteignez l\'appareil, puis rallumez-le"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio filaire"</string>
<string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index d15b6e22e135..6a22a9ba34c1 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -239,10 +239,10 @@
<string name="category_work" msgid="4014193632325996115">"Traballo"</string>
<string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
- <string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
- <string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
+ <string name="development_settings_title" msgid="140296922921597393">"Opcións de programación"</string>
+ <string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións de programación"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Definir as opcións de desenvolvemento de aplicacións"</string>
- <string name="development_settings_not_available" msgid="355070198089140951">"As opcións para programadores non están dispoñibles para este usuario"</string>
+ <string name="development_settings_not_available" msgid="355070198089140951">"As opcións de programación non están dispoñibles para este usuario"</string>
<string name="vpn_settings_not_available" msgid="2894137119965668920">"A configuración da VPN non está dispoñible para este usuario"</string>
<string name="tethering_settings_not_available" msgid="266821736434699780">"A configuración da conexión compartida non está dispoñible para este usuario"</string>
<string name="apn_settings_not_available" msgid="1147111671403342300">"A configuración do nome do punto de acceso non está dispoñible para este usuario"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tableta"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta televisión"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altofalante da base"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado mediante ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado mediante eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Opción predeterminada da televisión"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altofalantes internos"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altofalante integrado"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio da televisión"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Produciuse un problema coa conexión. Apaga e acende o dispositivo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Axuda e comentarios"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 5fd187f06c3c..91518d36cdee 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"આ ફોન"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"આ ટૅબ્લેટ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"આ કમ્પ્યૂટર (આંતરિક)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"આ ટીવી"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ડૉક સ્પીકર"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"બહારનું ડિવાઇસ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"કનેક્ટ કરેલું ડિવાઇસ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC મારફતે કનેક્ટેડ"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC મારફતે કનેક્ટેડ"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ડિવાઇસનું ડિફૉલ્ટ ઑડિયો આઉટપુટ, ટીવી"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI આઉટપુટ"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"આંતરિક સ્પીકર"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"બિલ્ટ-ઇન સ્પીકર"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ટીવીનો ઑડિયો"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"કનેક્ટ કરવામાં સમસ્યા આવી રહી છે. ડિવાઇસને બંધ કરીને ફરી ચાલુ કરો"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"વાયરવાળો ઑડિયો ડિવાઇસ"</string>
<string name="help_label" msgid="3528360748637781274">"સહાય અને પ્રતિસાદ"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index fa0716e4ad22..10a4622568f9 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"यह फ़ोन"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"यह टैबलेट"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"यह कंप्यूटर (इंटरनल)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"यह टीवी"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डॉक स्पीकर"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाहरी डिवाइस"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट किया गया डिवाइस"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"एचडीएमआई eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC से कनेक्ट किए गए डिवाइस"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC से कनेक्ट किए गए डिवाइस"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"टीवी की डिफ़ॉल्ट सेटिंग"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"एचडीएमआई आउटपुट"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"इंटरनल स्पीकर"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"डिवाइस में पहले से मौजूद स्पीकर"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टीवी ऑडियो"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"कनेक्ट करने में समस्या हो रही है. डिवाइस को बंद करके चालू करें"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"वायर वाला ऑडियो डिवाइस"</string>
<string name="help_label" msgid="3528360748637781274">"सहायता और सुझाव"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 8ad5c9d559d7..c3284ae4f400 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovo računalo (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj televizor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik priključne stanice"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Vanjski uređaj"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano putem ARC-a"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano putem eARC-a"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Zadano za TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Unutarnji zvučnici"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV zvuk"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem s povezivanjem. Isključite i ponovo uključite uređaj"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audiouređaj"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 18c6ea307651..dcf1610ccad4 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ez a telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ez a táblagép"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ez a számítógép (belső)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ez a TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokkhangszóró"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Külső eszköz"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Csatlakoztatott eszköz"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Csatlakoztatva ARC-n keresztül"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Csatlakoztatva eARC-n keresztül"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tévé alapértelmezett eszköze"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-kimenet"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Belső hangszórók"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Beépített hangszóró"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tévés hangkimenet"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Sikertelen csatlakozás. Kapcsolja ki az eszközt, majd kapcsolja be újra."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vezetékes audioeszköz"</string>
<string name="help_label" msgid="3528360748637781274">"Súgó és visszajelzés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 593479a4c011..0b933a33c3f8 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Այս հեռախոսը"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Այս պլանշետը"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Այս համակարգիչը (ներքին)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Այս հեռուստացույցը"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Դոկ-կայանով բարձրախոս"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Արտաքին սարք"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Միացված սարք"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Միացված է ARC-ի միջոցով"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Միացված է eARC-ի միջոցով"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Հեռուստացույցի կանխադրված կարգավորումներ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ելք"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ներքին բարձրախոսներ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ներկառուցված բարձրախոս"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Հեռուստացույցի աուդիո"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Կապի խնդիր կա: Սարքն անջատեք և նորից միացրեք:"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Լարով աուդիո սարք"</string>
<string name="help_label" msgid="3528360748637781274">"Օգնություն և հետադարձ կապ"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 6522e22734b9..cd5350755f7e 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ponsel ini"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tablet ini"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Komputer ini (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV ini"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Speaker dok"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Perangkat Eksternal"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Perangkat yang terhubung"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Terhubung melalui ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Terhubung melalui eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV sebagai default"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Output HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Speaker internal"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Speaker bawaan"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ada masalah saat menghubungkan. Nonaktifkan perangkat &amp; aktifkan kembali"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Perangkat audio berkabel"</string>
<string name="help_label" msgid="3528360748637781274">"Bantuan &amp; masukan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 4a10fb3e94d3..ff4e38b3f2de 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Þessi sími"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Þessi spjaldtölva"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Þessi tölva (innbyggður)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Þetta sjónvarp"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Hátalaradokka"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ytra tæki"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Tengt tæki"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Tengt með ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Tengt með eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sjálfgefið í sjónvarpi"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-úttak"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Innri hátalarar"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Innbyggður hátalari"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sjónvarpshljóð"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Vandamál í tengingu. Slökktu og kveiktu á tækinu"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Snúrutengt hljómtæki"</string>
<string name="help_label" msgid="3528360748637781274">"Hjálp og ábendingar"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 21f78ddbdb72..183a7990171f 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Questo smartphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Questo tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Questo computer (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Questa TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Base con altoparlante"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo esterno"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo connesso"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"eARC HDMI"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connessione tramite ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connessione tramite eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV predefinita"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Uscita HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altoparlanti interni"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altoparlante integrato"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio della TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema di connessione. Spegni e riaccendi il dispositivo"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo audio cablato"</string>
<string name="help_label" msgid="3528360748637781274">"Guida e feedback"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 3b28bd2745e3..c2b9cbc2a68d 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"הטלפון הזה"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"הטאבלט הזה"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"המחשב הזה (פנימי)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"הטלוויזיה הזו"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"רמקול של אביזר העגינה"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"מכשיר חיצוני"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"המכשיר המחובר"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏חיבור דרך ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏חיבור דרך eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ברירת המחדל של הטלוויזיה"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏פלט HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"רמקולים פנימיים"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"רמקול מובנה"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"אודיו בטלוויזיה"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"יש בעיה בחיבור. עליך לכבות את המכשיר ולהפעיל אותו מחדש"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"התקן אודיו חוטי"</string>
<string name="help_label" msgid="3528360748637781274">"עזרה ומשוב"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index a826810508e5..081ab48d511c 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"このデバイス"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"このタブレット"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"このパソコン(内蔵)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"このテレビ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ホルダー スピーカー"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部デバイス"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"接続済みのデバイス"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC 経由で接続済み"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC 経由で接続済み"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"テレビのデフォルト"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 出力"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"内蔵スピーカー"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"内蔵スピーカー"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV オーディオ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"接続エラーです。デバイスを OFF にしてから ON に戻してください"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線オーディオ デバイス"</string>
<string name="help_label" msgid="3528360748637781274">"ヘルプとフィードバック"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index f321371a744e..0ca04a19c809 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ეს ტელეფონი"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ამ ტაბლეტზე"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ეს კომპიუტერი (შიდა)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ეს ტელევიზორი"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"სამაგრის დინამიკი"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"გარე მოწყობილობა"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"დაკავშირებული მოწყობილობა"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"დაკავშირებულია ARC-ის მეშვეობით"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"დაკავშირებულია eARC-ის მეშვეობით"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ტელევიზორის ნაგულისხმევი"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"გამომავალი HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"შიდა დინამიკები"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ჩაშენებული დინამიკი"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"დაკავშირებისას წარმოიქმნა პრობლემა. გამორთეთ და კვლავ ჩართეთ მოწყობილობა"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"სადენიანი აუდიო მოწყობილობა"</string>
<string name="help_label" msgid="3528360748637781274">"დახმარება და გამოხმაურება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 85b86d4ef392..87917af0d29c 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Осы телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Осы планшет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Осы компьютер (ішкі)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Осы теледидар"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Динамигі бар қондыру станциясы"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Сыртқы құрылғы"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Жалғанған құрылғы"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC арқылы жалғанған."</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC арқылы жалғанған."</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Теледидардың әдепкі шығысы"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI шығыс интерфейсі"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ішкі динамиктер"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ендірілген динамик"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Теледидардың аудио шығысы"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Байланыс орнату қатесі шығуып жатыр. Құрылғыны өшіріп, қайта қосыңыз."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Сымды аудио құрылғысы"</string>
<string name="help_label" msgid="3528360748637781274">"Анықтама және пікір"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 12d35cf78685..2398ca81a03d 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ទូរសព្ទនេះ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ថេប្លេតនេះ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"កុំព្យូទ័រនេះ (ខាងក្នុង)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ទូរទស្សន៍​នេះ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ឧបាល័រជើងទម្រ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ឧបករណ៍ខាងក្រៅ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"​ឧបករណ៍ដែលបាន​ភ្ជាប់"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"បានភ្ជាប់តាមរយៈ ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"បានភ្ជាប់តាមរយៈ eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"លំនាំដើម​ទូរទស្សន៍"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"ធាតុចេញ HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ឧបករណ៍បំពងសំឡេងខាងក្នុង"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ឧបករណ៍បំពងសំឡេង​ភ្ជាប់មកជាមួយ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"សំឡេងទូរទស្សន៍"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"មាន​បញ្ហា​ក្នុងការ​ភ្ជាប់។ បិទ រួច​បើក​ឧបករណ៍​វិញ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ឧបករណ៍​សំឡេងប្រើខ្សែ"</string>
<string name="help_label" msgid="3528360748637781274">"ជំនួយ និងមតិកែលម្អ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index d95846fba071..690add8f0b1c 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ಈ ಫೋನ್"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ಈ ಟ್ಯಾಬ್ಲೆಟ್"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ಈ ಕಂಪ್ಯೂಟರ್ (ಆಂತರಿಕ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ಈ ಟಿವಿ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ಡಾಕ್ ಸ್ಪೀಕರ್"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ಬಾಹ್ಯ ಸಾಧನ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"ಕನೆಕ್ಟ್ ಮಾಡಿರುವ ಸಾಧನ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ಮೂಲಕ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ಮೂಲಕ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ಟಿವಿ ಡೀಫಾಲ್ಟ್"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ಔಟ್‌‌ಪುಟ್"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ಆಂತರಿಕ ಸ್ಪೀಕರ್‌ಗಳು"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ಅಂತರ್ ನಿರ್ಮಿತ ಧ್ವನಿ ವರ್ಧಕ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV ಆಡಿಯೊ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ಕನೆಕ್ಟ್ ಮಾಡುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ ಸಾಧನವನ್ನು ಆಫ್ ಮಾಡಿ ಹಾಗೂ ನಂತರ ಪುನಃ ಆನ್ ಮಾಡಿ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ವೈರ್ ಹೊಂದಿರುವ ಆಡಿಯೋ ಸಾಧನ"</string>
<string name="help_label" msgid="3528360748637781274">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index f82a6a6ec442..3f3fa5470e3f 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"이 휴대전화"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"이 태블릿"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"이 컴퓨터(내부)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"이 TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"도크 스피커"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"외부 기기"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"연결된 기기"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC를 통해 연결됨"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC를 통해 연결됨"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV 기본값"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 출력"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"내부 스피커"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"내장 스피커"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV 오디오"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"연결 중에 문제가 발생했습니다. 기기를 껐다가 다시 켜 보세요."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"유선 오디오 기기"</string>
<string name="help_label" msgid="3528360748637781274">"고객센터"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index ff63c1922940..db5525202f4c 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ушул телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ушул планшет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Бул компьютер (ички)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ушул сыналгы"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Док бекеттин динамиги"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Тышкы түзмөк"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Туташкан түзмөк"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC аркылуу туташты"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC аркылуу туташты"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Cыналгыдагы демейки түзмөк"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI аудио түзмөгү"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ички динамиктер"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Алдын ала орнотулган динамик"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Сыналгы аудиосу"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Туташууда маселе келип чыкты. Түзмөктү өчүрүп, кайра күйгүзүп көрүңүз"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Зымдуу аудио түзмөк"</string>
<string name="help_label" msgid="3528360748637781274">"Жардам/Пикир билдирүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 36d2ccf041db..f60894b5242a 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ໂທລະສັບນີ້"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ແທັບເລັດນີ້"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ຄອມພິວເຕີນີ້ (ພາຍໃນ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ໂທລະທັດນີ້"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ແທ່ນວາງລຳໂພງ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ອຸປະກອນພາຍນອກ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"ອຸປະກອນທີ່ເຊື່ອມຕໍ່"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ເຊື່ອມຕໍ່ຜ່ານ ARC ແລ້ວ"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"ເຊື່ອມຕໍ່ຜ່ານ eARC ແລ້ວ"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ຄ່າເລີ່ມຕົ້ນສຳລັບໂທລະທັດ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"ເອົ້າພຸດ HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ລຳໂພງຂອງເຄື່ອງ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ລຳໂພງທີ່ມີໃນຕົວ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ສຽງນີ້"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ເກີດບັນຫາໃນການເຊື່ອມຕໍ່. ປິດອຸປະກອນແລ້ວເປີດກັບຄືນມາໃໝ່"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ອຸປະກອນສຽງແບບມີສາຍ"</string>
<string name="help_label" msgid="3528360748637781274">"ຊ່ວຍເຫຼືອ ແລະ ຕິຊົມ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 5b20db3cd464..2932d21cba56 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis telefonas"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetinis kompiuteris"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šis kompiuteris (vidinis)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Šis televizorius"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doko garsiakalbis"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Išorinis įrenginys"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Prijungtas įrenginys"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI „eARC“"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Prisijungta per ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Prisijungta per „eARC“"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV numatytoji išvestis"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI išvestis"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Vidiniai garsiakalbiai"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Įtaisytas garsiakalbis"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV garso įrašas"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Prisijungiant kilo problema. Išjunkite įrenginį ir vėl jį įjunkite"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Laidinis garso įrenginys"</string>
<string name="help_label" msgid="3528360748637781274">"Pagalba ir atsiliepimai"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 620318a08f18..2f497fbc76b6 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis tālrunis"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetdators"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šī datora iekšējais skaļrunis"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Šis televizors"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doka skaļrunis"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ārēja ierīce"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Pievienotā ierīce"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Savienojums izveidots, izmantojot ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Savienojums izveidots, izmantojot eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Noklusējuma iestatījums televizorā"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izvade"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Iekšējie skaļruņi"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Iebūvēts skaļrunis"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Televizora audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Radās problēma ar savienojuma izveidi. Izslēdziet un atkal ieslēdziet ierīci."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vadu audioierīce"</string>
<string name="help_label" msgid="3528360748637781274">"Palīdzība un atsauksmes"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0331ece3c4dc..54d1ff15481e 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -359,7 +359,7 @@
<string name="enable_terminal_summary" msgid="2481074834856064500">"Овозможи апликација на терминал што овозможува локален пристап кон школка."</string>
<string name="enable_linux_terminal_title" msgid="5076044866895670637">"Програмерска околина на Linux"</string>
<string name="enable_linux_terminal_summary" msgid="2029479880888108902">"(Експериментално) Вклучете го Linux-терминалот на Android"</string>
- <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"Ако оневозможите, податоците за Linux-терминалот ќе се избришат"</string>
+ <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"Ако го оневозможите, податоците за Linux-терминалот ќе се избришат"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"Проверка со HDCP"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Постави однесување на проверка на HDCP"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"Отстранување грешки"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Овој телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Овој таблет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Овој компјуер (внатрешен)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Овој телевизор"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Док со звучник"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Надворешен уред"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Поврзан уред"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Поврзано преку ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Поврзано преку eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандарден излез на телевизор"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Излез за HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внатрешни звучници"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вграден звучник"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудио на телевизор"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Проблем со поврзување. Исклучете го уредот и повторно вклучете го"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Жичен аудиоуред"</string>
<string name="help_label" msgid="3528360748637781274">"Помош и повратни информации"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index aaebe4cd6abf..19c380b50cb8 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ഈ ഫോൺ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ഈ ടാബ്‌ലെറ്റ്"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ഈ കമ്പ്യൂട്ടർ (ഇന്റേണൽ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ഈ ടിവി"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ഡോക്ക് സ്‌പീക്കർ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ബാഹ്യ ഉപകരണം"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"കണക്‌റ്റ് ചെയ്‌ത ഉപകരണം"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC വഴി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC വഴി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ടിവി ഡിഫോൾട്ട്"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ഔട്ട്പുട്ട്"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ആന്തരിക സ്‌പീക്കറുകൾ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ബിൽട്ട്-ഇൻ സ്പീക്കർ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ടിവി ഓഡിയോ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"കണക്‌റ്റ് ചെയ്യുന്നതിൽ പ്രശ്‌നമുണ്ടായി. ഉപകരണം ഓഫാക്കി വീണ്ടും ഓണാക്കുക"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"വയർ മുഖേന ബന്ധിപ്പിച്ച ഓഡിയോ ഉപകരണം"</string>
<string name="help_label" msgid="3528360748637781274">"സഹായവും ഫീഡ്‌ബാക്കും"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index fb2b7291bb88..0e3408e1c5de 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Энэ утас"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Энэ таблет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Энэ компьютер (дотоод)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Энэ ТВ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Суурилуулагчийн чанга яригч"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Гадаад төхөөрөмж"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Холбогдсон төхөөрөмж"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC-р холбогдсон"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC-р холбогдсон"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ТВ-ийн өгөгдмөл"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI гаралт"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Дотоод чанга яригч"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Бүрэлдэхүүн чанга яригч"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ТВ-ийн аудио"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Холбогдоход асуудал гарлаа. Төхөөрөмжийг унтраагаад дахин асаана уу"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Утастай аудио төхөөрөмж"</string>
<string name="help_label" msgid="3528360748637781274">"Тусламж, санал хүсэлт"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 023a2e47e8f3..9b7c99c7fd61 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"हा फोन"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"हा टॅबलेट"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"हा काँप्युटर (अंतर्गत)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"हा टीव्ही"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डॉक स्पीकर"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाह्य डिव्हाइस"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट केलेले डिव्हाइस"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC द्वारे कनेक्ट केलेली"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC द्वारे कनेक्ट केलेली"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"टीव्ही डीफॉल्ट"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI आउटपुट"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"अंतर्गत स्पीकर"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"बिल्ट-इन स्पीकर"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टीव्ही ऑडिओ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"कनेक्‍ट करण्‍यात समस्‍या आली. डिव्हाइस बंद करा आणि नंतर सुरू करा"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"वायर असलेले ऑडिओ डिव्हाइस"</string>
<string name="help_label" msgid="3528360748637781274">"मदत आणि फीडबॅक"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 4212eb49f2cf..7c5d3f7599b9 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Telefon ini"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tablet ini"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Komputer ini (dalaman)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV ini"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Pembesar suara dok"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Peranti Luar"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Peranti yang disambungkan"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Disambungkan melalui ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Disambungkan melalui eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tetapan lalai TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Output HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Pembesar suara dalaman"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Pembesar suara terbina dalam"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Masalah penyambungan. Matikan &amp; hidupkan kembali peranti"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Peranti audio berwayar"</string>
<string name="help_label" msgid="3528360748637781274">"Bantuan &amp; maklum balas"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index df13e6c5f33e..0bd6ca6c2b27 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ဤဖုန်း"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ဤတက်ဘလက်"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ဤကွန်ပျူတာ (စက်တွင်း)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ဤ TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"အထိုင် စပီကာ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ပြင်ပစက်"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"ချိတ်ဆက်ကိရိယာ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV မူရင်း"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI အထွက်"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"စက်ရှိ စပီကာများ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"မူလပါရှိသည့် စပီကာ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV အသံ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ချိတ်ဆက်ရာတွင် ပြဿနာရှိပါသည်။ စက်ကိုပိတ်ပြီး ပြန်ဖွင့်ပါ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ကြိုးတပ် အသံစက်ပစ္စည်း"</string>
<string name="help_label" msgid="3528360748637781274">"အကူအညီနှင့် အကြံပြုချက်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 812666350505..ab14606b695c 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -499,7 +499,7 @@
<string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Mer enn <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="4144004473976005214">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – lader"</string>
<string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Fulladet innen <xliff:g id="TIME">%3$s</xliff:g>"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefonen"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dette nettbrettet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne datamaskinen (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Denne TV-en"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokkhøyttaler"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ekstern enhet"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Tilkoblet enhet"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Tilkoblet via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Tilkoblet via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV-ens standardinnstilling"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-utgang"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne høyttalere"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Innebygd høyttaler"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-lyd"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Tilkoblingsproblemer. Slå enheten av og på igjen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhet med kabel"</string>
<string name="help_label" msgid="3528360748637781274">"Hjelp og tilbakemelding"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index a233622362b8..50665f6f7897 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"यो फोन"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"यो ट्याब्लेट"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"यो कम्प्युटर (आन्तरिक)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"यो टिभी"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डक स्पिकर"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाह्य डिभाइस"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट गरिएको डिभाइस"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC मार्फत कनेक्ट गरिएका"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC मार्फत कनेक्ट गरिएका"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"टिभीको डिफल्ट अडियो आउटपुट"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI आउटपुट"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"आन्तरिक स्पिकरहरू"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"अन्तर्निर्मित स्पिकर"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टिभीको अडियो"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"जोड्ने क्रममा समस्या भयो। यन्त्रलाई निष्क्रिय पारेर फेरि अन गर्नुहोस्"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"तारयुक्त अडियो यन्त्र"</string>
<string name="help_label" msgid="3528360748637781274">"मद्दत र प्रतिक्रिया"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 551097d9e442..035c2d9e759d 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Deze telefoon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Deze tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Deze computer (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Deze tv"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockspeaker"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Extern apparaat"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Verbonden apparaat"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Verbonden via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Verbonden via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tv-standaard"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-uitvoer"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne speakers"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ingebouwde speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tv-audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Probleem bij verbinding maken. Zet het apparaat uit en weer aan."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Bedraad audioapparaat"</string>
<string name="help_label" msgid="3528360748637781274">"Hulp en feedback"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 790acffe2b80..894273703dca 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ଏହି ଫୋନ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ଏହି ଟାବଲେଟ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ଏହି କମ୍ପ୍ୟୁଟର (ଇଣ୍ଟର୍ନଲ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ଏହି ଟିଭି"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ଡକ ସ୍ପିକର"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ଏକ୍ସଟର୍ନଲ ଡିଭାଇସ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"କନେକ୍ଟ କରାଯାଇଥିବା ଡିଭାଇସ"</string>
@@ -602,14 +601,13 @@
<string name="media_output_status_device_in_low_power_mode" msgid="8184631698321758451">"ଏଠାରେ ପ୍ଲେ କରିବା ପାଇଁ ଡିଭାଇସକୁ ସକ୍ରିୟ କରନ୍ତୁ"</string>
<string name="media_output_status_unauthorized" msgid="5880222828273853838">"ପ୍ଲେ କରିବା ପାଇଁ ଡିଭାଇସକୁ ଅନୁମୋଦନ କରାଯାଇନାହିଁ"</string>
<string name="media_output_status_track_unsupported" msgid="5576313219317709664">"ଏଠାରେ ମିଡିଆ ପ୍ଲେ କରାଯାଇପାରିବ ନାହିଁ"</string>
- <string name="tv_media_transfer_connected" msgid="5145011475885290725">"କନେକ୍ଟ କରାଯାଇଛି"</string>
+ <string name="tv_media_transfer_connected" msgid="5145011475885290725">"କନେକ୍ଟ ହୋଇଛି"</string>
<string name="tv_media_transfer_arc_fallback_title" msgid="3674360098755328601">"HDMI ARC"</string>
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ମାଧ୍ୟମରେ କନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ମାଧ୍ୟମରେ କନେକ୍ଟ କରାଯାଇଛି"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ଟିଭିର ଡିଫଲ୍ଟ ଅଡିଓ ଆଉଟପୁଟ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ଆଉଟପୁଟ"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ଇଣ୍ଟର୍ନଲ ସ୍ପିକର"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ବିଲ୍ଟ-ଇନ ସ୍ପିକର"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ଟିଭି ଅଡିଓ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ସଂଯୋଗ କରିବାରେ ସମସ୍ୟା ହେଉଛି। ଡିଭାଇସ୍ ବନ୍ଦ କରି ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ତାରଯୁକ୍ତ ଅଡିଓ ଡିଭାଇସ୍"</string>
<string name="help_label" msgid="3528360748637781274">"ସାହାଯ୍ୟ ଓ ମତାମତ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index c20e0d88ee3f..14e87aa68f04 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ਇਹ ਫ਼ੋਨ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ਇਹ ਟੈਬਲੈੱਟ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ਇਸ ਕੰਪਿਊਟਰ \'ਤੇ (ਅੰਦਰੂਨੀ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ਇਹ ਟੀਵੀ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ਡੌਕ ਸਪੀਕਰ"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ਬਾਹਰੀ ਡੀਵਾਈਸ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"ਕਨੈਕਟ ਕੀਤਾ ਡੀਵਾਈਸ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤੇ ਗਏ ਡੀਵਾਈਸ"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤੇ ਗਏ ਡੀਵਾਈਸ"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ਟੀਵੀ ਦਾ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਆਊਟਪੁੱਟ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ਆਊਟਪੁੱਟ"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ਅੰਦਰੂਨੀ ਸਪੀਕਰ"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ਬਿਲਟ-ਇਨ ਸਪੀਕਰ"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ਟੀਵੀ ਆਡੀਓ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ਕਨੈਕਟ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ ਆਈ। ਡੀਵਾਈਸ ਨੂੰ ਬੰਦ ਕਰਕੇ ਵਾਪਸ ਚਾਲੂ ਕਰੋ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ਤਾਰ ਵਾਲਾ ਆਡੀਓ ਡੀਵਾਈਸ"</string>
<string name="help_label" msgid="3528360748637781274">"ਮਦਦ ਅਤੇ ਵਿਚਾਰ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f6ab8a7c19a4..eaa12bd004a0 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ten telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ten tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ten komputer (wewnętrzny)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ten telewizor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Głośnik ze stacją dokującą"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Urządzenie zewnętrzne"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Połączone urządzenie"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Połączono przez ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Połączono przez eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Ustawienie domyślne telewizora"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Wyjście HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Głośniki wewnętrzne"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Wbudowany głośnik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Telewizyjne urządzenie audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem z połączeniem. Wyłącz i ponownie włącz urządzenie"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Przewodowe urządzenie audio"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoc i opinie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index dedc65c1369f..afa7129db3b7 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telefone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Padrão da TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Alto-falantes internos"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Alto-falante integrado"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index f1ac88bdda40..986467a5df9c 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telemóvel"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altifalante estação carregamento"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo associado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ligado através de ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ligado através de eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predefinição da TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altifalantes internos"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altifalante integrado"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema ao ligar. Desligue e volte a ligar o dispositivo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fios"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index dedc65c1369f..afa7129db3b7 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telefone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Padrão da TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Alto-falantes internos"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Alto-falante integrado"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index cafb7881a32a..b64047c5e13b 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Acest telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Această tabletă"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Acest computer (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Acest televizor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Difuzorul dispozitivului de andocare"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispozitiv extern"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispozitiv conectat"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectat prin ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectat prin eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Prestabilit pentru televizor"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Ieșire HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Difuzoare interne"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Difuzor încorporat"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problemă la conectare. Oprește și repornește dispozitivul."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispozitiv audio cu fir"</string>
<string name="help_label" msgid="3528360748637781274">"Ajutor și feedback"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 951f3a0dd146..8c9ecb11b086 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Этот смартфон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Этот планшет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Встроенное"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Этот телевизор"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Колонка с док-станцией"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Внешнее устройство"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Подключенное устройство"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Подключено через ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Подключено через eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартный выход на телевизоре"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Выход HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внутренние динамики"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Встроенный динамик"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудиовыход телевизора"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ошибка подключения. Выключите и снова включите устройство."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Проводное аудиоустройство"</string>
<string name="help_label" msgid="3528360748637781274">"Справка/отзыв"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 8427064b99ef..6c9abfb949f5 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"මෙම දුරකථනය"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"මෙම ටැබ්ලටය"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"මෙම පරිගණකය (අභ්‍යන්තර)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"මෙම රූපවාහිනිය"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ඩොක් ස්පීකරය"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"බාහිර උපාංගය"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"සම්බන්ධ කළ උපාංගය"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC හරහා සම්බන්ධ කර ඇත"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC හරහා සම්බන්ධ කර ඇත"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"රූපවාහිනී පෙරනිමිය"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ප්‍රතිදානය"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"අභ්‍යන්තර ස්පීකර්"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"එකට තැනූ ශබ්දවාහිනීය"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"රූපවාහිනී ශ්‍රව්‍ය"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"සම්බන්ධ කිරීමේ ගැටලුවකි උපාංගය ක්‍රියාවිරහිත කර &amp; ආපසු ක්‍රියාත්මක කරන්න"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"රැහැන්ගත කළ ඕඩියෝ උපාංගය"</string>
<string name="help_label" msgid="3528360748637781274">"උදවු &amp; ප්‍රතිපෝෂණ"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index aca07e5a32c3..05c744234204 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefón"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interný)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tento televízor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Reproduktor doku"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externé zariadenie"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Pripojené zariadenie"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Pripojené prostredníctvom rozhrania ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Pripojené prostredníctvom rozhrania eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predvolené nastavenie televízora"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Výstup HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interné reproduktory"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vstavaný reproduktor"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk televízora"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Pri pripájaní sa vyskytol problém. Zariadenie vypnite a znova zapnite."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio zariadenie s káblom"</string>
<string name="help_label" msgid="3528360748637781274">"Pomocník a spätná väzba"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 00d93a65d2f1..c0e4f811bd54 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ta telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ta tablični računalnik"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ta računalnik (notranji)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ta televizor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvočnik nosilca"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Zunanja naprava"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezana naprava"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano prek zvočnega kanala ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano prek zvočnega kanala eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Privzeti izhod televizorja"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Izhod HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Notranji zvočniki"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vgrajen zvočnik"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvok televizorja"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Težava pri povezovanju. Napravo izklopite in znova vklopite."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žična zvočna naprava"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoč in povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c907e22c7784..2676d0ee083c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -313,7 +313,7 @@
<string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Aktivizo kodekun e audios me Bluetooth\nZgjedhja: Bite për shembull"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Modaliteti i kanalit të audios me Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Aktivizo kodekun e audios me Bluetooth\nZgjedhja: Modaliteti i kanalit"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Kodeku LDAC i audios së Bluetooth-it: Cilësia e luajtjes"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Kodeku LDAC i audios me Bluetooth: Cilësia e luajtjes"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Aktivizo LDAC të audios me Bluetooth\nZgjedhja e kodekut: Cilësia e luajtjes"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Transmetimi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"DNS-ja private"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ky telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ky tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ky kompjuter (i brendshëm)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ky televizor"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altoparlanti i stacionit"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Pajisja e jashtme"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Pajisja e lidhur"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Lidhur përmes ARC-së"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Lidhur përmes eARC-së"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Parazgjedhja e televizorit"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Dalja HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altoparlantët e brendshëm"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altoparlanti i integruar"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audioja e televizorit"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem me lidhjen. Fike dhe ndize përsëri pajisjen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Pajisja audio me tel"</string>
<string name="help_label" msgid="3528360748637781274">"Ndihma dhe komentet"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 65000e468fea..8fce0a0bc6c7 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Овај телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Овај таблет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Овај рачунар (интерно)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Овај ТВ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Звучник базне станице"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Спољни уређај"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Повезани уређај"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Повезано преко ARC-а"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Повезано преко eARC-а"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Подразумевана вредност за ТВ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI излаз"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Унутрашњи звучници"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Уграђени звучник"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Звук ТВ-а"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Проблем при повезивању. Искључите уређај, па га поново укључите"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Жичани аудио уређај"</string>
<string name="help_label" msgid="3528360748637781274">"Помоћ и повратне информације"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 12c17290bf15..c7ce0f8b4c2a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Den här telefonen"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Den här surfplattan"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Den här datorn (intern)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Den här tv:n"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockningsstationens högtalare"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Extern enhet"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ansluten enhet"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ansluten via ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ansluten via eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardutgång för tv:n"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-utgång"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interna högtalare"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Inbyggd högtalare"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tv-ljud"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Det gick inte att ansluta. Stäng av enheten och slå på den igen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Ljudenhet med kabelanslutning"</string>
<string name="help_label" msgid="3528360748637781274">"Hjälp och feedback"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1bf5677668ca..3bee28aae4ee 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Simu hii"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Kishikwambi hiki"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Kompyuta hii (spika ya ndani)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Televisheni hii"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Spika ya kituo"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Kifaa cha Nje"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Kifaa kilichounganishwa"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Imeunganishwa kupitia ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Imeunganishwa kupitia eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Mipangilio Chaguomsingi ya TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Kutoa sauti kupitia HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Spika za ndani"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Spika iliyojumuishwa ndani"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sauti ya Televisheni"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Kuna tatizo la kuunganisha kwenye Intaneti. Zima kisha uwashe kifaa"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kifaa cha sauti kinachotumia waya"</string>
<string name="help_label" msgid="3528360748637781274">"Usaidizi na maoni"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 8ee8fa22c61f..289947ee2e77 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"இந்த மொபைல்"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"இந்த டேப்லெட்"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"இந்தக் கம்ப்யூட்டர் (அகம்)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"இந்த டிவி"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"டாக் ஸ்பீக்கர்"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"வெளிப்புறச் சாதனம்"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"இணைக்கப்பட்டுள்ள சாதனம்"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC மூலம் இணைக்கப்பட்டுள்ளவை"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC மூலம் இணைக்கப்பட்டுள்ளவை"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"டிவி இயல்புநிலை"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI அவுட்புட்"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"உட்புற ஸ்பீக்கர்கள்"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"உள்ளமைந்த ஸ்பீக்கர்"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"டிவி ஆடியோ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"இணைப்பதில் சிக்கல். சாதனத்தை ஆஃப் செய்து மீண்டும் ஆன் செய்யவும்"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"வயருடன்கூடிய ஆடியோ சாதனம்"</string>
<string name="help_label" msgid="3528360748637781274">"உதவியும் கருத்தும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 46350d5bf30c..d6683bb293c1 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ఈ ఫోన్"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ఈ టాబ్లెట్"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ఈ కంప్యూటర్ (ఇంటర్నల్)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ఈ టీవీ"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"డాక్ స్పీకర్"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"ఎక్స్‌టర్నల్ పరికరం"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"కనెక్ట్ చేసిన పరికరం"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ద్వారా కనెక్ట్ చేయబడింది"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ద్వారా కనెక్ట్ చేయబడింది"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"టీవీ ఆటోమేటిక్ సెట్టింగ్"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI అవుట్‌పుట్"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"అంతర్గత స్పీకర్‌లు"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"బిల్ట్-ఇన్ స్పీకర్"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"టీవీ ఆడియో"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"కనెక్ట్ చేయడంలో సమస్య ఉంది. పరికరాన్ని ఆఫ్ చేసి, ఆపై తిరిగి ఆన్ చేయండి"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"వైర్ గల ఆడియో పరికరం"</string>
<string name="help_label" msgid="3528360748637781274">"సహాయం &amp; ఫీడ్‌బ్యాక్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 460782af8457..6095c5009ef5 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -304,7 +304,7 @@
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"เลือกเวอร์ชันของบลูทูธ AVRCP"</string>
<string name="bluetooth_select_map_version_string" msgid="526308145174175327">"เวอร์ชันของบลูทูธ MAP"</string>
<string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"เลือกเวอร์ชันของบลูทูธ MAP"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ตัวแปลงสัญญาณเสียงบลูทูธ"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ตัวแปลงรหัสเสียงบลูทูธ"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"อัตราตัวอย่างเสียงบลูทูธ"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: อัตราตัวอย่าง"</string>
@@ -313,7 +313,7 @@
<string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: บิตต่อตัวอย่าง"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"โหมดช่องสัญญาณเสียงบลูทูธ"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: โหมดช่องสัญญาณ"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"ตัวแปลงสัญญาณเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"ทริกเกอร์การเลือกตัวแปลงรหัส LDAC\nเสียงบลูทูธ: คุณภาพการเล่น"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"DNS ส่วนตัว"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"โทรศัพท์เครื่องนี้"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"แท็บเล็ตเครื่องนี้"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"คอมพิวเตอร์เครื่องนี้ (ภายใน)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ทีวีเครื่องนี้"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"แท่นชาร์จที่มีลำโพง"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"อุปกรณ์ภายนอก"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"อุปกรณ์ที่เชื่อมต่อ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"เชื่อมต่อผ่าน ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"เชื่อมต่อผ่าน eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"ค่าเริ่มต้นของทีวี"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"เอาต์พุต HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ลำโพงของเครื่อง"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ลำโพงในตัว"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"เสียงจากทีวี"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"เกิดปัญหาในการเชื่อมต่อ ปิดอุปกรณ์แล้วเปิดใหม่อีกครั้ง"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"อุปกรณ์เสียงแบบมีสาย"</string>
<string name="help_label" msgid="3528360748637781274">"ความช่วยเหลือและความคิดเห็น"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 986b7771d9f5..4eb5c1e428bf 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ang teleponong ito"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ang tablet na ito"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Sa computer na ito (internal)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ang TV na ito"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Speaker ng dock"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"External na Device"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Nakakonektang device"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Nakakonekta sa pamamagitan ng ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Nakakonekta sa pamamagitan ng eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Default ng TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Mga internal speaker"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in na speaker"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio ng TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Nagkaproblema sa pagkonekta. I-off at pagkatapos ay i-on ang device"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired na audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Tulong at feedback"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index df47dacc7d7b..705b71453856 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu tablet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu bilgisayar (dahili)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Bu TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Yuva hoparlörü"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Harici Cihaz"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Bağlı cihaz"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ile bağlandı"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ile bağlandı"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV varsayılanı"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI çıkışı"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Dahili hoparlörler"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Dahili hoparlör"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Sesi"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Bağlanırken sorun oluştu. Cihazı kapatıp tekrar açın"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kablolu ses cihazı"</string>
<string name="help_label" msgid="3528360748637781274">"Yardım ve geri bildirim"</string>
diff --git a/packages/SettingsLib/res/values-uk/arrays.xml b/packages/SettingsLib/res/values-uk/arrays.xml
index 6032efbf3ed1..234af3680512 100644
--- a/packages/SettingsLib/res/values-uk/arrays.xml
+++ b/packages/SettingsLib/res/values-uk/arrays.xml
@@ -257,12 +257,12 @@
<item msgid="1212561935004167943">"Тест. команди малювання зеленим"</item>
</string-array>
<string-array name="track_frame_time_entries">
- <item msgid="634406443901014984">"Вимк."</item>
+ <item msgid="634406443901014984">"Вимкнено"</item>
<item msgid="1288760936356000927">"На екрані у вигляді смужок"</item>
<item msgid="5023908510820531131">"У команді \"<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>\""</item>
</string-array>
<string-array name="debug_hw_overdraw_entries">
- <item msgid="1968128556747588800">"Вимк."</item>
+ <item msgid="1968128556747588800">"Вимкнено"</item>
<item msgid="3033215374382962216">"Показувати області накладання"</item>
<item msgid="3474333938380896988">"Показувати області дейтераномалії"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 3ba99a2f443e..dda905ececa4 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Цей телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Цей планшет"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Цей комп’ютер (внутрішній)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Цей телевізор"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Динамік док-станції"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Зовнішній пристрій"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Підключений пристрій"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Підключено через ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Підключено через eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартний вихід телевізора"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Вихід HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внутрішні динаміки"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вбудований динамік"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудіо з телевізора"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Не вдається підключитися. Перезавантажте пристрій."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Дротовий аудіопристрій"</string>
<string name="help_label" msgid="3528360748637781274">"Довідка й відгуки"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e5e29faae8f5..204663da79d0 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"یہ فون"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"یہ ٹیبلیٹ"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"یہ کمپیوٹر (داخلی)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"‏یہ TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ڈاک اسپیکر"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"بیرونی آلہ"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"منسلک آلہ"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏ARC کے ذریعے منسلک ہے"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏eARC کے ذریعے منسلک ہے"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"‏TV ڈیفالٹ"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏HDMI آؤٹ پٹ"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"اندرونی اسپیکرز"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"پہلے سے شامل اسپیکر"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"‏‫TV آڈیو"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"منسلک کرنے میں مسئلہ پیش آ گیا۔ آلہ کو آف اور بیک آن کریں"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"وائرڈ آڈیو آلہ"</string>
<string name="help_label" msgid="3528360748637781274">"مدد اور تاثرات"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 2417d5556bee..a7500a36e6fc 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Shu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Shu planshet"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompyuter (ichki)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Shu TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dok-stansiyali karnay"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Tashqi qurilma"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ulangan qurilma"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC orqali ulangan"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC orqali ulangan"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Televizorda birlamchi"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI chiqishi"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ichki karnaylar"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ichki karnay"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ulanishda muammo yuz berdi. Qurilmani oʻchiring va yoqing"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Simli audio qurilma"</string>
<string name="help_label" msgid="3528360748637781274">"Yordam/fikr-mulohaza"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 0239a3f2c39d..5d620e62d772 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Điện thoại này"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Máy tính bảng này"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Máy tính này (nội bộ)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV này"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Loa có gắn đế"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Thiết bị bên ngoài"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Thiết bị đã kết nối"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Đã kết nối qua ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Đã kết nối qua eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"Chế độ mặc định của TV"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Đầu ra HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Các loa trong"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Loa tích hợp"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Âm thanh TV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Sự cố kết nối. Hãy tắt thiết bị rồi bật lại"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Thiết bị âm thanh có dây"</string>
<string name="help_label" msgid="3528360748637781274">"Trợ giúp và phản hồi"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 22bfda4fe6fc..724899972297 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -359,7 +359,7 @@
<string name="enable_terminal_summary" msgid="2481074834856064500">"启用终端应用,以便在本地访问 Shell"</string>
<string name="enable_linux_terminal_title" msgid="5076044866895670637">"Linux 开发环境"</string>
<string name="enable_linux_terminal_summary" msgid="2029479880888108902">"(实验性)在 Android 上运行 Linux 终端"</string>
- <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"如果您停用它,Linux 终端数据会被清除"</string>
+ <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"停用后,Linux 终端数据会被清除"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 检查"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"设置 HDCP 检查行为"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"调试"</string>
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"这部手机"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"这部平板电脑"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此计算机(内部)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"此电视"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"基座音箱"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部设备"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"连接的设备"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"已通过 ARC 连接"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"已通过 eARC 连接"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"电视默认设置"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 输出"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"内置扬声器"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"内置扬声器"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"电视音频"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"连接时遇到问题。请关闭并重新开启设备"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有线音频设备"</string>
<string name="help_label" msgid="3528360748637781274">"帮助和反馈"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 594273a327ce..6a8d6d5c2835 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"此手機"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"此平板電腦"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此電腦 (內置)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"這部電視"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"插座喇叭"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部裝置"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"已連接的裝置"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"已透過 ARC 連接"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"已透過 eARC 連接"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"電視預設的音訊輸出"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 輸出"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"內置喇叭"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"內置喇叭"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"電視音訊"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"無法連接,請關閉裝置然後重新開機"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線音響裝置"</string>
<string name="help_label" msgid="3528360748637781274">"說明與意見反映"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 483dbf30b31c..c5eb0bb268b7 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"這支手機"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"這台平板電腦"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"這部電腦 (內部)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"這部電視"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"座架喇叭"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部裝置"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"已連結的裝置"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"透過 ARC 連線"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"透過 eARC 連線"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"電視預設的音訊輸出裝置"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 輸出裝置"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"內建揚聲器"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"內建喇叭"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"電視音訊"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"無法連線,請關閉裝置後再重新開啟"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線音訊裝置"</string>
<string name="help_label" msgid="3528360748637781274">"說明與意見回饋"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 2bb3f225565e..f467a3a46921 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -586,8 +586,7 @@
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Le foni"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Le thebhulethi"</string>
<string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Le khompyutha (ngaphakathi)"</string>
- <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
- <skip />
+ <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Le TV"</string>
<string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Isipikha sentuba"</string>
<string name="media_transfer_external_device_name" msgid="2588672258721846418">"Idivayisi Yangaphandle"</string>
<string name="media_transfer_default_device_name" msgid="4315604017399871828">"Idivayisi exhunyiwe"</string>
@@ -607,9 +606,8 @@
<string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"I-HDMI eARC"</string>
<string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ixhunywe nge-ARC"</string>
<string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ixhunywe nge-eARC"</string>
- <string name="tv_media_transfer_default" msgid="5403053145185843843">"I-TV ezenzakalelayo"</string>
- <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"umphumela we-HDMI"</string>
- <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Izipikha zangaphakathi"</string>
+ <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Isipikha esakhelwe ngaphakathi"</string>
+ <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Umsondo weTV"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Inkinga yokuxhumeka. Vala idivayisi futhi uphinde uyivule"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Idivayisi yomsindo enentambo"</string>
<string name="help_label" msgid="3528360748637781274">"Usizo nempendulo"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index fc163ce79009..4de64769b425 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -27,8 +27,10 @@ import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
-import android.app.ecm.EnhancedConfirmationManager;
+import android.app.admin.EnforcingAdmin;
import android.app.admin.PackagePolicy;
+import android.app.admin.UnknownAuthority;
+import android.app.ecm.EnhancedConfirmationManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
@@ -43,6 +45,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
+import android.security.advancedprotection.AdvancedProtectionManager;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
@@ -202,6 +205,14 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
return null;
}
+ if (android.security.Flags.aapmApi()) {
+ EnforcingAdmin admin = dpm.getEnforcingAdmin(userId, userRestriction);
+ if (admin != null) {
+ return new EnforcedAdmin(admin.getComponentName(), userRestriction,
+ admin.getUserHandle());
+ }
+ }
+
final EnforcedAdmin admin =
getProfileOrDeviceOwner(context, userRestriction, enforcingUser.getUserHandle());
if (admin != null) {
@@ -838,6 +849,22 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
}
/**
+ * Checks if the identifier is enforced by advanced protection.
+ */
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ public static boolean isPolicyEnforcedByAdvancedProtection(Context context, String identifier,
+ int userId) {
+ if (!android.security.Flags.aapmApi()) return false;
+ if (identifier == null) return false;
+ EnforcingAdmin admin = context.getSystemService(DevicePolicyManager.class)
+ .getEnforcingAdmin(userId, identifier);
+ if (admin == null) return false;
+ return admin.getAuthority() instanceof UnknownAuthority authority
+ && AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY.equals(
+ authority.getName());
+ }
+
+ /**
* Check if there are restrictions on an application from being a Credential Manager provider.
*
* @return EnforcedAdmin Object containing the enforced admin component and admin user details,
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 6ca9279de8c6..25628fba1b66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -33,7 +33,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
@@ -118,10 +117,7 @@ public class RestrictedPreferenceHelper {
if (mDisabledSummary) {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
- final CharSequence disabledText =
- (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
- ? getDisabledByAdminUpdatableString()
- : mContext.getString(R.string.disabled_by_admin_summary_text);
+ final CharSequence disabledText = getDisabledByAdminSummaryString();
if (mDisabledByAdmin) {
summaryView.setText(disabledText);
} else if (mDisabledByEcm) {
@@ -134,11 +130,23 @@ public class RestrictedPreferenceHelper {
}
}
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
- private String getDisabledByAdminUpdatableString() {
- return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
- CONTROLLED_BY_ADMIN_SUMMARY,
- () -> mContext.getString(R.string.disabled_by_admin_summary_text));
+ private String getDisabledByAdminSummaryString() {
+ if (isRestrictionEnforcedByAdvancedProtection()) {
+ return mContext.getString(com.android.settingslib.widget.restricted
+ .R.string.disabled_by_advanced_protection);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+ CONTROLLED_BY_ADMIN_SUMMARY,
+ () -> mContext.getString(R.string.disabled_by_admin_summary_text));
+ }
+ return mContext.getString(R.string.disabled_by_admin_summary_text);
+ }
+
+ public boolean isRestrictionEnforcedByAdvancedProtection() {
+ return mEnforcedAdmin != null && RestrictedLockUtilsInternal
+ .isPolicyEnforcedByAdvancedProtection(mContext, mEnforcedAdmin.enforcedRestriction,
+ UserHandle.myUserId());
}
public void useAdminDisabledSummary(boolean useSummary) {
@@ -226,17 +234,24 @@ public class RestrictedPreferenceHelper {
*/
public boolean setDisabledByAdmin(EnforcedAdmin admin) {
boolean disabled = false;
+ boolean changed = false;
+ EnforcedAdmin previousAdmin = mEnforcedAdmin;
mEnforcedAdmin = null;
if (admin != null) {
disabled = true;
// Copy the received instance to prevent pass be reference being overwritten.
mEnforcedAdmin = new EnforcedAdmin(admin);
+ if (android.security.Flags.aapmApi()) {
+ changed = previousAdmin == null || !previousAdmin.equals(admin);
+ }
}
- boolean changed = false;
if (mDisabledByAdmin != disabled) {
mDisabledByAdmin = disabled;
changed = true;
+ }
+
+ if (changed) {
updateDisabledState();
}
@@ -286,6 +301,10 @@ public class RestrictedPreferenceHelper {
((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
}
+ if (android.security.Flags.aapmApi() && !isEnabled && mDisabledByAdmin) {
+ mPreference.setSummary(getDisabledByAdminSummaryString());
+ }
+
if (!isEnabled && mDisabledByEcm) {
mPreference.setSummary(R.string.disabled_by_app_ops_text);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 727dbe1019ae..0aac9a1104e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -126,13 +126,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
CharSequence switchSummary;
if (mRestrictedSwitchSummary == null) {
- switchSummary = isChecked()
- ? getUpdatableEnterpriseString(
- getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
- com.android.settingslib.widget.restricted.R.string.enabled_by_admin)
- : getUpdatableEnterpriseString(
- getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
- com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
+ switchSummary = getRestrictedSwitchSummary();
} else {
switchSummary = mRestrictedSwitchSummary;
}
@@ -177,6 +171,25 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement
() -> context.getString(resId));
}
+ private 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);
+ }
+
+ return isChecked()
+ ? getUpdatableEnterpriseString(
+ getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+ com.android.settingslib.widget.restricted.R.string.enabled_by_admin)
+ : getUpdatableEnterpriseString(
+ getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+ com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
+ }
+
@Override
public void performClick() {
if (!mHelper.performClick()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 216574a5fff9..429e4c958f05 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -631,15 +631,15 @@ public class BluetoothUtils {
assistantProfile.getAllConnectedDevices().stream()
.map(deviceManager::findDevice)
.filter(Objects::nonNull)
- .map(CachedBluetoothDevice::getGroupId)
+ .map(BluetoothUtils::getGroupId)
.collect(Collectors.toSet());
Set<Integer> activeGroupIds =
leAudioProfile.getActiveDevices().stream()
.map(deviceManager::findDevice)
.filter(Objects::nonNull)
- .map(CachedBluetoothDevice::getGroupId)
+ .map(BluetoothUtils::getGroupId)
.collect(Collectors.toSet());
- int groupId = cachedDevice.getGroupId();
+ int groupId = getGroupId(cachedDevice);
return activeGroupIds.size() == 1
&& !activeGroupIds.contains(groupId)
&& connectedGroupIds.size() == 2
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
new file mode 100644
index 000000000000..7a64965334c9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
@@ -0,0 +1,408 @@
+/*
+ * 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.bluetooth;
+
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The class to manage hearing device local data from Settings.
+ *
+ * <p><b>Note:</b> Before calling any methods to get or change the local data, you must first call
+ * the {@code start()} method to load the data from Settings. Whenever the data is modified, you
+ * must call the {@code stop()} method to save the data into Settings. After calling {@code stop()},
+ * you should not call any methods to get or change the local data without again calling
+ * {@code start()}.
+ */
+public class HearingDeviceLocalDataManager {
+ private static final String TAG = "HearingDeviceDataMgr";
+ private static final boolean DEBUG = true;
+
+ /** Interface for listening hearing device local data changed */
+ public interface OnDeviceLocalDataChangeListener {
+ /**
+ * The method is called when the local data of the device with the address is changed.
+ *
+ * @param address the device anonymized address
+ * @param data the updated data
+ */
+ void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data);
+ }
+
+ static final String KEY_ADDR = "addr";
+ static final String KEY_AMBIENT = "ambient";
+ static final String KEY_GROUP_AMBIENT = "group_ambient";
+ static final String KEY_AMBIENT_CONTROL_EXPANDED = "control_expanded";
+ static final String LOCAL_AMBIENT_VOLUME_SETTINGS =
+ Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME;
+
+ private static final Object sLock = new Object();
+
+ private final Context mContext;
+ private Executor mListenerExecutor;
+ @GuardedBy("sLock")
+ private final Map<String, Data> mAddrToDataMap = new HashMap<>();
+ private OnDeviceLocalDataChangeListener mListener;
+ private SettingsObserver mSettingsObserver;
+ private boolean mIsStarted = false;
+
+ public HearingDeviceLocalDataManager(@NonNull Context context) {
+ mContext = context;
+ mSettingsObserver = new SettingsObserver(ThreadUtils.getUiThreadHandler());
+ }
+
+ /** Starts the manager. Loads the data from Settings and start observing any changes. */
+ public synchronized void start() {
+ if (mIsStarted) {
+ return;
+ }
+ mIsStarted = true;
+ getLocalDataFromSettings();
+ mSettingsObserver.register(mContext.getContentResolver());
+ }
+
+ /** Stops the manager. Flushes the data into Settings and stop observing. */
+ public synchronized void stop() {
+ if (!mIsStarted) {
+ return;
+ }
+ putAmbientVolumeSettings();
+ mSettingsObserver.unregister(mContext.getContentResolver());
+ mIsStarted = false;
+ }
+
+ /**
+ * Sets a listener which will be be notified when hearing device local data is changed.
+ *
+ * @param listener the listener to be notified
+ * @param executor the executor to run the
+ * {@link OnDeviceLocalDataChangeListener#onDeviceLocalDataChange(String,
+ * Data)} callback
+ */
+ public void setOnDeviceLocalDataChangeListener(
+ @NonNull OnDeviceLocalDataChangeListener listener, @NonNull Executor executor) {
+ mListener = listener;
+ mListenerExecutor = executor;
+ }
+
+ /**
+ * Gets the local data of the corresponding hearing device. This should be called after
+ * {@link #start()} is called().
+ *
+ * @param device the device to query the local data
+ */
+ @NonNull
+ public Data get(@NonNull BluetoothDevice device) {
+ if (!mIsStarted) {
+ Log.w(TAG, "Manager is not started. Please call start() first.");
+ return new Data();
+ }
+ synchronized (sLock) {
+ return mAddrToDataMap.getOrDefault(device.getAnonymizedAddress(), new Data());
+ }
+ }
+
+ /**
+ * Puts the local data of the corresponding hearing device.
+ *
+ * @param device the device to update the local data
+ */
+ private void put(BluetoothDevice device, Data data) {
+ if (device == null) {
+ return;
+ }
+ synchronized (sLock) {
+ final String addr = device.getAnonymizedAddress();
+ mAddrToDataMap.put(addr, data);
+ if (mListener != null && mListenerExecutor != null) {
+ mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data));
+ }
+ }
+ }
+
+ /**
+ * Updates the ambient volume of the corresponding hearing device. This should be called after
+ * {@link #start()} is called().
+ *
+ * @param device the device to update
+ * @param value the ambient value
+ * @return if the local data is updated
+ */
+ public boolean updateAmbient(@Nullable BluetoothDevice device, int value) {
+ if (!mIsStarted) {
+ Log.w(TAG, "Manager is not started. Please call start() first.");
+ return false;
+ }
+ if (device == null) {
+ return false;
+ }
+ synchronized (sLock) {
+ Data data = get(device);
+ if (value == data.ambient) {
+ return false;
+ }
+ put(device, new Data.Builder(data).ambient(value).build());
+ return true;
+ }
+ }
+
+ /**
+ * Updates the group ambient volume of the corresponding hearing device. This should be called
+ * after {@link #start()} is called().
+ *
+ * @param device the device to update
+ * @param value the group ambient value
+ * @return if the local data is updated
+ */
+ public boolean updateGroupAmbient(@Nullable BluetoothDevice device, int value) {
+ if (!mIsStarted) {
+ Log.w(TAG, "Manager is not started. Please call start() first.");
+ return false;
+ }
+ if (device == null) {
+ return false;
+ }
+ synchronized (sLock) {
+ Data data = get(device);
+ if (value == data.groupAmbient) {
+ return false;
+ }
+ put(device, new Data.Builder(data).groupAmbient(value).build());
+ return true;
+ }
+ }
+
+ /**
+ * Updates the ambient control is expanded or not of the corresponding hearing device. This
+ * should be called after {@link #start()} is called().
+ *
+ * @param device the device to update
+ * @param expanded the ambient control is expanded or not
+ * @return if the local data is updated
+ */
+ public boolean updateAmbientControlExpanded(@Nullable BluetoothDevice device,
+ boolean expanded) {
+ if (!mIsStarted) {
+ Log.w(TAG, "Manager is not started. Please call start() first.");
+ return false;
+ }
+ if (device == null) {
+ return false;
+ }
+ synchronized (sLock) {
+ Data data = get(device);
+ if (expanded == data.ambientControlExpanded) {
+ return false;
+ }
+ put(device, new Data.Builder(data).ambientControlExpanded(expanded).build());
+ return true;
+ }
+ }
+
+ void getLocalDataFromSettings() {
+ synchronized (sLock) {
+ Map<String, Data> updatedAddrToDataMap = parseFromSettings();
+ notifyIfDataChanged(mAddrToDataMap, updatedAddrToDataMap);
+ mAddrToDataMap.clear();
+ mAddrToDataMap.putAll(updatedAddrToDataMap);
+ if (DEBUG) {
+ Log.v(TAG, "getLocalDataFromSettings, " + mAddrToDataMap + ", manager: " + this);
+ }
+ }
+ }
+
+ void putAmbientVolumeSettings() {
+ synchronized (sLock) {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry<String, Data> entry : mAddrToDataMap.entrySet()) {
+ builder.append(KEY_ADDR).append("=").append(entry.getKey());
+ builder.append(entry.getValue().toSettingsFormat()).append(";");
+ }
+ if (DEBUG) {
+ Log.v(TAG, "putAmbientVolumeSettings, " + builder + ", manager: " + this);
+ }
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(),
+ UserHandle.USER_SYSTEM);
+ }
+ }
+
+ @GuardedBy("sLock")
+ private Map<String, Data> parseFromSettings() {
+ String settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+ LOCAL_AMBIENT_VOLUME_SETTINGS, UserHandle.USER_SYSTEM);
+ Map<String, Data> addrToDataMap = new ArrayMap<>();
+ if (settings != null && !settings.isEmpty()) {
+ String[] localDataArray = settings.split(";");
+ for (String localData : localDataArray) {
+ KeyValueListParser parser = new KeyValueListParser(',');
+ parser.setString(localData);
+ String address = parser.getString(KEY_ADDR, "");
+ if (!address.isEmpty()) {
+ Data data = new Data.Builder()
+ .ambient(parser.getInt(KEY_AMBIENT, INVALID_VOLUME))
+ .groupAmbient(parser.getInt(KEY_GROUP_AMBIENT, INVALID_VOLUME))
+ .ambientControlExpanded(
+ parser.getBoolean(KEY_AMBIENT_CONTROL_EXPANDED, false))
+ .build();
+ addrToDataMap.put(address, data);
+ }
+ }
+ }
+ return addrToDataMap;
+ }
+
+ @GuardedBy("sLock")
+ private void notifyIfDataChanged(Map<String, Data> oldAddrToDataMap,
+ Map<String, Data> newAddrToDataMap) {
+ newAddrToDataMap.forEach((addr, data) -> {
+ Data oldData = oldAddrToDataMap.get(addr);
+ if (oldData == null || !oldData.equals(data)) {
+ if (mListener != null) {
+ mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data));
+ }
+ }
+ });
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ private final Uri mAmbientVolumeUri = Settings.Global.getUriFor(
+ LOCAL_AMBIENT_VOLUME_SETTINGS);
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void register(ContentResolver contentResolver) {
+ contentResolver.registerContentObserver(mAmbientVolumeUri, false, this,
+ UserHandle.USER_SYSTEM);
+ }
+
+ void unregister(ContentResolver contentResolver) {
+ contentResolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (mAmbientVolumeUri.equals(uri)) {
+ Log.v(TAG, "Local data on change, manager: " + HearingDeviceLocalDataManager.this);
+ getLocalDataFromSettings();
+ }
+ }
+ }
+
+ public record Data(int ambient, int groupAmbient, boolean ambientControlExpanded) {
+
+ public static int INVALID_VOLUME = Integer.MIN_VALUE;
+
+ private Data() {
+ this(INVALID_VOLUME, INVALID_VOLUME, false);
+ }
+
+ /**
+ * Return {@code true} if one of {@link #ambient} or {@link #groupAmbient} is assigned to
+ * a valid value.
+ */
+ public boolean hasAmbientData() {
+ return ambient != INVALID_VOLUME || groupAmbient != INVALID_VOLUME;
+ }
+
+ /**
+ * @return the composed string which is used to store the local data in
+ * {@link Settings.Global#HEARING_DEVICE_LOCAL_AMBIENT_VOLUME}
+ */
+ @NonNull
+ public String toSettingsFormat() {
+ String string = "";
+ if (ambient != INVALID_VOLUME) {
+ string += ("," + KEY_AMBIENT + "=" + ambient);
+ }
+ if (groupAmbient != INVALID_VOLUME) {
+ string += ("," + KEY_GROUP_AMBIENT + "=" + groupAmbient);
+ }
+ string += ("," + KEY_AMBIENT_CONTROL_EXPANDED + "=" + ambientControlExpanded);
+ return string;
+ }
+
+ /** Builder for a Data object */
+ public static final class Builder {
+ private int mAmbient;
+ private int mGroupAmbient;
+ private boolean mAmbientControlExpanded;
+
+ public Builder() {
+ this.mAmbient = INVALID_VOLUME;
+ this.mGroupAmbient = INVALID_VOLUME;
+ this.mAmbientControlExpanded = false;
+ }
+
+ public Builder(@NonNull Data other) {
+ this.mAmbient = other.ambient;
+ this.mGroupAmbient = other.groupAmbient;
+ this.mAmbientControlExpanded = other.ambientControlExpanded;
+ }
+
+ /** Sets the ambient volume */
+ @NonNull
+ public Builder ambient(int ambient) {
+ this.mAmbient = ambient;
+ return this;
+ }
+
+ /** Sets the group ambient volume */
+ @NonNull
+ public Builder groupAmbient(int groupAmbient) {
+ this.mGroupAmbient = groupAmbient;
+ return this;
+ }
+
+ /** Sets the ambient control expanded */
+ @NonNull
+ public Builder ambientControlExpanded(boolean ambientControlExpanded) {
+ this.mAmbientControlExpanded = ambientControlExpanded;
+ return this;
+ }
+
+ /** Build the Data object */
+ @NonNull
+ public Data build() {
+ return new Data(mAmbient, mGroupAmbient, mAmbientControlExpanded);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index ab7a3db4b3bb..d85b92f0216b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -21,6 +21,7 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
+import android.bluetooth.AudioInputControl;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -34,6 +35,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
@@ -168,6 +170,7 @@ public class VolumeControlProfile implements LocalBluetoothProfile {
}
mService.setVolumeOffset(device, volumeOffset);
}
+
/**
* Provides information about the possibility to set volume offset on the remote device. If the
* remote device supports Volume Offset Control Service, it is automatically connected.
@@ -210,6 +213,22 @@ public class VolumeControlProfile implements LocalBluetoothProfile {
mService.setDeviceVolume(device, volume, isGroupOp);
}
+ /**
+ * Returns a list of {@link AudioInputControl} objects associated with a Bluetooth device.
+ *
+ * @param device The remote Bluetooth device.
+ * @return A list of {@link AudioInputControl} objects, or an empty list if no AICS instances
+ * are found or if an error occurs.
+ * @hide
+ */
+ public @NonNull List<AudioInputControl> getAudioInputControlServices(
+ @NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+ return mService.getAudioInputControlServices(device);
+ }
+
@Override
public boolean accessProfileEnabled() {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 0209eb8c3fbf..0474b50e9ac8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -121,6 +121,14 @@ class DeviceSettingServiceConnection(
null
}
}
+ .catch { e ->
+ if (e is DeadObjectException) {
+ Log.e(TAG, "DeadObjectException happens when try to get service status.", e)
+ emit(false)
+ } else {
+ throw e
+ }
+ }
.firstOrNull() ?: false
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 35e3dd3379f0..e1be1d21a9b1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@ public class MobileStatusTracker {
public SignalStrength signalStrength;
public TelephonyDisplayInfo telephonyDisplayInfo =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
/**
* Empty constructor
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 23be7baa6496..496c3e6c74cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -74,10 +74,6 @@ class FakeZenModeRepository : ZenModeRepository {
mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
}
- fun replaceMode(modeId: String, mode: ZenMode) {
- mutableModesFlow.value = (mutableModesFlow.value.filter { it.id != modeId }) + mode
- }
-
fun clearModes() {
mutableModesFlow.value = listOf()
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 6842d0a949af..abc163867248 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -41,32 +41,24 @@ public class TestModeBuilder {
private String mId;
private AutomaticZenRule mRule;
private ZenModeConfig.ZenRule mConfigZenRule;
+ private boolean mIsManualDnd;
public static final ZenMode EXAMPLE = new TestModeBuilder().build();
- public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY,
+ public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(
INTERRUPTION_FILTER_PRIORITY, true);
- public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY,
+ public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(
INTERRUPTION_FILTER_PRIORITY, false);
@NonNull
public static ZenMode manualDnd(@NotificationManager.InterruptionFilter int filter,
boolean isActive) {
- return manualDnd(Uri.EMPTY, filter, isActive);
- }
-
- private static ZenMode manualDnd(Uri conditionId,
- @NotificationManager.InterruptionFilter int filter, boolean isActive) {
- return ZenMode.manualDndMode(
- new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
- .setInterruptionFilter(filter)
- .setType(AutomaticZenRule.TYPE_OTHER)
- .setManualInvocationAllowed(true)
- .setPackage(SystemZenRules.PACKAGE_ANDROID)
- .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
- .build(),
- isActive);
+ return new TestModeBuilder()
+ .makeManualDnd()
+ .setInterruptionFilter(filter)
+ .setActive(isActive)
+ .build();
}
public TestModeBuilder() {
@@ -91,6 +83,10 @@ public class TestModeBuilder {
mConfigZenRule.enabled = previous.getRule().isEnabled();
mConfigZenRule.pkg = previous.getRule().getPackageName();
setActive(previous.isActive());
+
+ if (previous.isManualDnd()) {
+ makeManualDnd();
+ }
}
public TestModeBuilder setId(String id) {
@@ -222,7 +218,25 @@ public class TestModeBuilder {
return this;
}
+ public TestModeBuilder makeManualDnd() {
+ mIsManualDnd = true;
+ // Set the "fixed" properties of a DND mode. Other things, such as policy/filter may be set
+ // separately or copied from a preexisting DND, so they are not overwritten here.
+ setId(ZenMode.MANUAL_DND_MODE_ID);
+ setName("Do Not Disturb");
+ setType(AutomaticZenRule.TYPE_OTHER);
+ setManualInvocationAllowed(true);
+ setPackage(SystemZenRules.PACKAGE_ANDROID);
+ setConditionId(Uri.EMPTY);
+ return this;
+ }
+
public ZenMode build() {
- return new ZenMode(mId, mRule, mConfigZenRule);
+ if (mIsManualDnd) {
+ return ZenMode.manualDndMode(mRule, mConfigZenRule.condition != null
+ && mConfigZenRule.condition.state == Condition.STATE_TRUE);
+ } else {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
new file mode 100644
index 000000000000..b659c02a2540
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+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;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Tests for {@link HearingDeviceLocalDataManager}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {HearingDeviceLocalDataManagerTest.ShadowGlobal.class})
+public class HearingDeviceLocalDataManagerTest {
+
+ private static final String TEST_ADDRESS = "XX:XX:XX:XX:11:22";
+ private static final int TEST_AMBIENT = 10;
+ private static final int TEST_GROUP_AMBIENT = 20;
+ private static final boolean TEST_AMBIENT_CONTROL_EXPANDED = true;
+ private static final int TEST_UPDATED_AMBIENT = 30;
+ private static final int TEST_UPDATED_GROUP_AMBIENT = 40;
+ private static final boolean TEST_UPDATED_AMBIENT_CONTROL_EXPANDED = false;
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock
+ private BluetoothDevice mDevice;
+ @Mock
+ private HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener mListener;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private HearingDeviceLocalDataManager mLocalDataManager;
+
+ @Before
+ public void setUp() {
+ prepareTestDataInSettings();
+ mLocalDataManager = new HearingDeviceLocalDataManager(mContext);
+ mLocalDataManager.start();
+ mLocalDataManager.setOnDeviceLocalDataChangeListener(mListener,
+ mContext.getMainExecutor());
+
+ when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
+ }
+
+ @Test
+ public void stop_verifyDataIsSaved() {
+ mLocalDataManager.updateAmbient(mDevice, TEST_UPDATED_AMBIENT);
+ mLocalDataManager.stop();
+
+ String settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+ Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM);
+ String expectedSettings = generateSettingsString(TEST_ADDRESS, TEST_UPDATED_AMBIENT,
+ TEST_GROUP_AMBIENT, TEST_AMBIENT_CONTROL_EXPANDED);
+ assertThat(settings).isEqualTo(expectedSettings);
+ }
+
+ @Test
+ public void get_correctDataFromSettings() {
+ HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(mDevice);
+
+ assertThat(data.ambient()).isEqualTo(TEST_AMBIENT);
+ assertThat(data.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+ assertThat(data.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+ }
+
+ @Test
+ public void updateAmbient_correctValue_listenerCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+
+ mLocalDataManager.updateAmbient(mDevice, TEST_UPDATED_AMBIENT);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.ambient()).isEqualTo(TEST_UPDATED_AMBIENT);
+ verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+ }
+
+ @Test
+ public void updateAmbient_sameValue_listenerNotCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+
+ mLocalDataManager.updateAmbient(mDevice, TEST_AMBIENT);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.ambient()).isEqualTo(TEST_AMBIENT);
+ verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+ }
+
+ @Test
+ public void updateGroupAmbient_correctValue_listenerCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+
+ mLocalDataManager.updateGroupAmbient(mDevice, TEST_UPDATED_GROUP_AMBIENT);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.groupAmbient()).isEqualTo(TEST_UPDATED_GROUP_AMBIENT);
+ verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+ }
+
+ @Test
+ public void updateGroupAmbient_sameValue_listenerNotCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+
+ mLocalDataManager.updateGroupAmbient(mDevice, TEST_GROUP_AMBIENT);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+ verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+ }
+
+ @Test
+ public void updateAmbientControlExpanded_correctValue_listenerCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+ mLocalDataManager.updateAmbientControlExpanded(mDevice,
+ TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.ambientControlExpanded()).isEqualTo(
+ TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+ verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+ }
+
+ @Test
+ public void updateAmbientControlExpanded_sameValue_listenerNotCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+ mLocalDataManager.updateAmbientControlExpanded(mDevice, TEST_AMBIENT_CONTROL_EXPANDED);
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+ verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+ }
+
+ @Test
+ public void getLocalDataFromSettings_dataChanged_correctValue_listenerCalled() {
+ HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+ assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+ assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+ assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+ prepareUpdatedDataInSettings();
+ mLocalDataManager.getLocalDataFromSettings();
+
+ HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+ assertThat(newData.ambient()).isEqualTo(TEST_UPDATED_AMBIENT);
+ assertThat(newData.groupAmbient()).isEqualTo(TEST_UPDATED_GROUP_AMBIENT);
+ assertThat(newData.ambientControlExpanded()).isEqualTo(
+ TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+ verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+ }
+
+ private void prepareTestDataInSettings() {
+ String data = generateSettingsString(TEST_ADDRESS, TEST_AMBIENT, TEST_GROUP_AMBIENT,
+ TEST_AMBIENT_CONTROL_EXPANDED);
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, data,
+ UserHandle.USER_SYSTEM);
+ }
+
+ private void prepareUpdatedDataInSettings() {
+ String data = generateSettingsString(TEST_ADDRESS, TEST_UPDATED_AMBIENT,
+ TEST_UPDATED_GROUP_AMBIENT, TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, data,
+ UserHandle.USER_SYSTEM);
+ }
+
+ private String generateSettingsString(String addr, int ambient, int groupAmbient,
+ boolean ambientControlExpanded) {
+ return "addr=" + addr + ",ambient=" + ambient + ",group_ambient=" + groupAmbient
+ + ",control_expanded=" + ambientControlExpanded + ";";
+ }
+
+ @Implements(value = Settings.Global.class)
+ public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
+ private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
+
+ @Implementation
+ protected static boolean putStringForUser(
+ ContentResolver cr, String name, String value, int userHandle) {
+ get(cr).put(name, value);
+ return true;
+ }
+
+ @Implementation
+ protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
+ return get(cr).get(name);
+ }
+
+ private static Map<String, String> get(ContentResolver cr) {
+ return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index 9c518de18582..bd67394dcc62 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.AudioInputControl;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
@@ -45,6 +46,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
@@ -248,4 +250,16 @@ public class VolumeControlProfileTest {
verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
assertThat(available).isFalse();
}
+
+ @Test
+ public void getAudioInputControlServices_verifyIsCalledAndReturnNonNullList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getAudioInputControlServices(mBluetoothDevice)).thenReturn(new ArrayList<>());
+
+ final List<AudioInputControl> controls = mProfile.getAudioInputControlServices(
+ mBluetoothDevice);
+
+ verify(mService).getAudioInputControlServices(mBluetoothDevice);
+ assertThat(controls).isNotNull();
+ }
}
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
deleted file mode 100644
index 645b275e2af5..000000000000
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!--
- Default system bookmarks for AOSP.
- Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
-
- Typical shortcuts (not necessarily defined here):
- 'b': Browser
- 'c': Contacts
- 'e': Email
- 'g': GMail
- 'k': Calendar
- 'm': Maps
- 'p': Music
- 's': SMS
- 't': Talk
- 'u': Calculator
- 'y': YouTube
--->
-<bookmarks>
- <!-- TODO(b/358569822): Remove this from Settings DB
- This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
- no longer used -->
- <bookmark
- role="android.app.role.BROWSER"
- shortcut="b" />
- <bookmark
- category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
- <bookmark
- category="android.intent.category.APP_EMAIL"
- shortcut="e" />
- <bookmark
- category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
- <bookmark
- category="android.intent.category.APP_MAPS"
- shortcut="m" />
- <bookmark
- category="android.intent.category.APP_MUSIC"
- shortcut="p" />
- <bookmark
- role="android.app.role.SMS"
- shortcut="s" />
- <bookmark
- category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
-</bookmarks>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1f291cdefb03..731cb7269037 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -99,6 +99,7 @@ public class SecureSettings {
Settings.Secure.RTT_CALLING_MODE,
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
+ Settings.Secure.MIRROR_BUILT_IN_DISPLAY,
Settings.Secure.MATCH_CONTENT_FRAME_RATE,
Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 8e7180c4dc8d..935ea2549d49 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -119,7 +119,8 @@ public class SystemSettings {
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
Settings.System.NOTIFICATION_COOLDOWN_ALL,
- Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED
+ Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+ Settings.System.PREFERRED_REGION
));
if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
settings.add(Settings.System.PEAK_REFRESH_RATE);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index abd5b9a4a4bb..039832cee6f2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -150,6 +150,7 @@ public class SecureSettingsValidators {
Secure.INCALL_POWER_BUTTON_BEHAVIOR,
new DiscreteValueValidator(new String[] {"1", "2"}));
VALIDATORS.put(Secure.MINIMAL_POST_PROCESSING_ALLOWED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.MIRROR_BUILT_IN_DISPLAY, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.MATCH_CONTENT_FRAME_RATE,
new DiscreteValueValidator(new String[] {"0", "1", "2"}));
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index cfc7743f0a8d..63f401c63b93 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -230,6 +230,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.EGG_MODE,
@@ -265,5 +266,6 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.PREFERRED_REGION, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index e85ba453945b..e057682d6d95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -16,14 +16,8 @@
package com.android.providers.settings;
-import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -46,16 +40,11 @@ import android.util.Log;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
-import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.File;
-import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -85,7 +74,7 @@ class DatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private int mUserHandle;
- private static final HashSet<String> mValidTables = new HashSet<String>();
+ private static final HashSet<String> mValidTables = new HashSet<>();
private static final String DATABASE_BACKUP_SUFFIX = "-backup";
@@ -100,7 +89,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
// These are old.
mValidTables.add("bluetooth_devices");
- mValidTables.add("bookmarks");
mValidTables.add("favorites");
mValidTables.add("old_favorites");
mValidTables.add("android_metadata");
@@ -211,21 +199,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
"type INTEGER" +
");");
- db.execSQL("CREATE TABLE bookmarks (" +
- "_id INTEGER PRIMARY KEY," +
- "title TEXT," +
- "folder TEXT," +
- "intent TEXT," +
- "shortcut INTEGER," +
- "ordering INTEGER" +
- ");");
-
- db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);");
- db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);");
-
- // Populate bookmarks table with initial bookmarks
- loadBookmarks(db);
-
// Load initial volume levels into DB
loadVolumeLevels(db);
@@ -392,19 +365,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
if (upgradeVersion == 30) {
- /*
- * Upgrade 31 clears the title for all quick launch shortcuts so the
- * activities' titles will be resolved at display time. Also, the
- * folder is changed to '@quicklaunch'.
- */
- db.beginTransaction();
- try {
- db.execSQL("UPDATE bookmarks SET folder = '@quicklaunch'");
- db.execSQL("UPDATE bookmarks SET title = ''");
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
upgradeVersion = 31;
}
@@ -1006,8 +966,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
if (upgradeVersion == 70) {
- // Update all built-in bookmarks. Some of the package names have changed.
- loadBookmarks(db);
upgradeVersion = 71;
}
@@ -2046,92 +2004,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
/**
- * Loads the default set of bookmarked shortcuts from an xml file.
- *
- * @param db The database to write the values into
- */
- private void loadBookmarks(SQLiteDatabase db) {
- ContentValues values = new ContentValues();
-
- PackageManager packageManager = mContext.getPackageManager();
- try {
- XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
- XmlUtils.beginDocument(parser, "bookmarks");
-
- final int depth = parser.getDepth();
- int type;
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- String name = parser.getName();
- if (!"bookmark".equals(name)) {
- break;
- }
-
- String pkg = parser.getAttributeValue(null, "package");
- String cls = parser.getAttributeValue(null, "class");
- String shortcutStr = parser.getAttributeValue(null, "shortcut");
- String category = parser.getAttributeValue(null, "category");
-
- int shortcutValue = shortcutStr.charAt(0);
- if (TextUtils.isEmpty(shortcutStr)) {
- Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
- continue;
- }
-
- final Intent intent;
- final String title;
- if (pkg != null && cls != null) {
- ActivityInfo info = null;
- ComponentName cn = new ComponentName(pkg, cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e) {
- String[] packages = packageManager.canonicalToCurrentPackageNames(
- new String[] { pkg });
- cn = new ComponentName(packages[0], cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e1) {
- Log.w(TAG, "Unable to add bookmark: " + pkg + "/" + cls, e);
- continue;
- }
- }
-
- intent = new Intent(Intent.ACTION_MAIN, null);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(cn);
- title = info.loadLabel(packageManager).toString();
- } else if (category != null) {
- intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
- title = "";
- } else {
- Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutStr
- + ": missing package/class or category attributes");
- continue;
- }
-
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- values.put(Settings.Bookmarks.INTENT, intent.toUri(0));
- values.put(Settings.Bookmarks.TITLE, title);
- values.put(Settings.Bookmarks.SHORTCUT, shortcutValue);
- db.delete("bookmarks", "shortcut = ?",
- new String[] { Integer.toString(shortcutValue) });
- db.insert("bookmarks", null, values);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- } catch (IOException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- }
- }
-
- /**
* Loads the default volume levels. It is actually inserting the index of
* the volume array for each of the volume controls.
*
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
index 7eff16b0def4..0367fe0dab01 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
+++ b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.providers.settings;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 9ab853ff4964..326bff448193 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.FullBackupDataOutput;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -82,6 +83,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.HashMap;
import java.util.zip.CRC32;
/**
@@ -194,6 +196,22 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY =
"pin_enhanced_privacy";
+ // Error messages for logging metrics.
+ private static final String ERROR_COULD_NOT_READ_FROM_CURSOR =
+ "could_not_read_from_cursor";
+ private static final String ERROR_FAILED_TO_WRITE_ENTITY =
+ "failed_to_write_entity";
+ private static final String ERROR_COULD_NOT_READ_ENTITY =
+ "could_not_read_entity";
+ private static final String ERROR_SKIPPED_BY_SYSTEM = "skipped_by_system";
+ private static final String ERROR_SKIPPED_BY_BLOCKLIST =
+ "skipped_by_dynamic_blocklist";
+ private static final String ERROR_SKIPPED_PRESERVED = "skipped_preserved";
+ private static final String ERROR_SKIPPED_DUE_TO_LARGE_SCREEN =
+ "skipped_due_to_large_screen";
+ private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation";
+
+
// Name of the temporary file we use during full backup/restore. This is
// stored in the full-backup tarfile as well, so should not be changed.
private static final String STAGE_FILE = "flattened-data";
@@ -224,6 +242,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
// The font_scale default value for this device.
private float mDefaultFontScale;
+ @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger;
+ @VisibleForTesting boolean areAgentMetricsEnabled = false;
+ @VisibleForTesting protected Map<String, Integer> numberOfSettingsPerKey;
+
@Override
public void onCreate() {
if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
@@ -232,6 +254,11 @@ public class SettingsBackupAgent extends BackupAgentHelper {
.getStringArray(R.array.entryvalues_font_size);
mSettingsHelper = new SettingsHelper(this);
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) {
+ mBackupRestoreEventLogger = this.getBackupRestoreEventLogger();
+ numberOfSettingsPerKey = new HashMap<>();
+ areAgentMetricsEnabled = true;
+ }
super.onCreate();
}
@@ -356,7 +383,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
movedToSecure, /* movedToSystem= */ null,
R.array.restore_blocked_system_settings, dynamicBlockList,
- preservedSystemSettings);
+ preservedSystemSettings, KEY_SYSTEM);
mSettingsHelper.applyAudioSettings();
break;
@@ -364,13 +391,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal,
/* movedToSecure= */ null, movedToSystem,
R.array.restore_blocked_secure_settings, dynamicBlockList,
- preservedSecureSettings);
+ preservedSecureSettings, KEY_SECURE);
break;
case KEY_GLOBAL :
restoreSettings(data, Settings.Global.CONTENT_URI, /* movedToGlobal= */ null,
movedToSecure, movedToSystem, R.array.restore_blocked_global_settings,
- dynamicBlockList, preservedGlobalSettings);
+ dynamicBlockList, preservedGlobalSettings, KEY_GLOBAL);
break;
case KEY_WIFI_SUPPLICANT :
@@ -489,7 +516,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
movedToSecure, /* movedToSystem= */ null,
R.array.restore_blocked_system_settings, Collections.emptySet(),
- Collections.emptySet());
+ Collections.emptySet(), KEY_SYSTEM);
// secure settings
nBytes = in.readInt();
@@ -499,7 +526,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal,
/* movedToSecure= */ null, movedToSystem,
R.array.restore_blocked_secure_settings, Collections.emptySet(),
- Collections.emptySet());
+ Collections.emptySet(), KEY_SECURE);
// Global only if sufficiently new
if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -510,7 +537,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI,
/* movedToGlobal= */ null, movedToSecure, movedToSystem,
R.array.restore_blocked_global_settings, Collections.emptySet(),
- Collections.emptySet());
+ Collections.emptySet(), KEY_GLOBAL);
}
// locale
@@ -654,23 +681,41 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (oldChecksum == newChecksum) {
return oldChecksum;
}
+ writeDataForKey(key, data, output);
+ return newChecksum;
+ }
+
+ @VisibleForTesting
+ void writeDataForKey(String key, byte[] data, BackupDataOutput output) {
+ boolean shouldLogMetrics =
+ areAgentMetricsEnabled && numberOfSettingsPerKey.containsKey(key);
try {
if (DEBUG_BACKUP) {
Log.v(TAG, "Writing entity " + key + " of size " + data.length);
}
output.writeEntityHeader(key, data.length);
output.writeEntityData(data, data.length);
+ if (shouldLogMetrics) {
+ mBackupRestoreEventLogger
+ .logItemsBackedUp(key, numberOfSettingsPerKey.get(key));
+ }
} catch (IOException ioe) {
// Bail
+ if (shouldLogMetrics) {
+ mBackupRestoreEventLogger
+ .logItemsBackupFailed(
+ key,
+ numberOfSettingsPerKey.get(key),
+ ERROR_FAILED_TO_WRITE_ENTITY);
+ }
}
- return newChecksum;
}
private byte[] getSystemSettings() {
Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
null, null);
try {
- return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP);
+ return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP, KEY_SYSTEM);
} finally {
cursor.close();
}
@@ -680,7 +725,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
null, null);
try {
- return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP);
+ return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP, KEY_SECURE);
} finally {
cursor.close();
}
@@ -690,7 +735,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
null, null);
try {
- return extractRelevantValues(cursor, getGlobalSettingsToBackup());
+ return extractRelevantValues(cursor, getGlobalSettingsToBackup(), KEY_GLOBAL);
} finally {
cursor.close();
}
@@ -773,7 +818,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
return baos.toByteArray();
}
- private void restoreSettings(
+ @VisibleForTesting
+ void restoreSettings(
BackupDataInput data,
Uri contentUri,
Set<String> movedToGlobal,
@@ -781,12 +827,17 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
- Set<String> settingsToPreserve) {
+ Set<String> settingsToPreserve,
+ String settingsKey) {
byte[] settings = new byte[data.getDataSize()];
try {
data.readEntityData(settings, 0, settings.length);
} catch (IOException ioe) {
Log.e(TAG, "Couldn't read entity data");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ settingsKey, /* count= */ 1, ERROR_COULD_NOT_READ_ENTITY);
+ }
return;
}
restoreSettings(
@@ -798,7 +849,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
- settingsToPreserve);
+ settingsToPreserve,
+ settingsKey);
}
private void restoreSettings(
@@ -810,7 +862,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
- Set<String> settingsToPreserve) {
+ Set<String> settingsToPreserve,
+ String settingsKey) {
restoreSettings(
settings,
0,
@@ -821,7 +874,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
- settingsToPreserve);
+ settingsToPreserve,
+ settingsKey);
}
@VisibleForTesting
@@ -835,12 +889,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
- Set<String> settingsToPreserve) {
+ Set<String> settingsToPreserve,
+ String settingsKey) {
if (DEBUG) {
Log.i(TAG, "restoreSettings: " + contentUri);
}
- SettingsBackupWhitelist whitelist = getBackupWhitelist(contentUri);
+ SettingsBackupAllowlist allowlist = getBackupAllowlist(contentUri);
// Restore only the white list data.
final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
@@ -850,7 +905,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);
- for (String key : whitelist.mSettingsWhitelist) {
+ int restoredSettingsCount = 0;
+ for (String key : allowlist.mSettingsAllowlist) {
boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) {
Log.i(
@@ -860,6 +916,12 @@ public class SettingsBackupAgent extends BackupAgentHelper {
+ " removed from restore by "
+ (isBlockedBySystem ? "system" : "dynamic")
+ " block list");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ settingsKey,
+ /* count= */ 1,
+ isBlockedBySystem ? ERROR_SKIPPED_BY_SYSTEM : ERROR_SKIPPED_BY_BLOCKLIST);
+ }
continue;
}
@@ -870,12 +932,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (isSettingPreserved && !Settings.Secure.NAVIGATION_MODE.equals(key)) {
Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as "
+ "preserved");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ settingsKey, /* count= */ 1, ERROR_SKIPPED_PRESERVED);
+ }
continue;
}
if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+ "is a large screen (i.e tablet or foldable in unfolded state)");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ settingsKey, /* count= */ 1, ERROR_SKIPPED_DUE_TO_LARGE_SCREEN);
+ }
continue;
}
@@ -912,19 +982,34 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
// only restore the settings that have valid values
- if (!isValidSettingValue(key, value, whitelist.mSettingsValidators)) {
+ if (!isValidSettingValue(key, value, allowlist.mSettingsValidators)) {
Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass"
+ " validation, value: " + value);
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ settingsKey, /* count= */ 1, ERROR_DID_NOT_PASS_VALIDATION);
+ }
continue;
}
final Uri destination;
+ // If the destination changes, we need to update the key used as datatype for metrics.
+ String finalSettingsKey = settingsKey;
if (movedToGlobal != null && movedToGlobal.contains(key)) {
destination = Settings.Global.CONTENT_URI;
+ if (areAgentMetricsEnabled) {
+ finalSettingsKey = KEY_GLOBAL;
+ }
} else if (movedToSecure != null && movedToSecure.contains(key)) {
destination = Settings.Secure.CONTENT_URI;
+ if (areAgentMetricsEnabled) {
+ finalSettingsKey = KEY_SECURE;
+ }
} else if (movedToSystem != null && movedToSystem.contains(key)) {
destination = Settings.System.CONTENT_URI;
+ if (areAgentMetricsEnabled) {
+ finalSettingsKey = KEY_SYSTEM;
+ }
} else {
destination = contentUri;
}
@@ -942,6 +1027,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (isSettingPreserved) {
Log.i(TAG, "Skipping restore for setting navigation_mode "
+ "as it is marked as preserved");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ finalSettingsKey, /* count= */ 1, ERROR_SKIPPED_PRESERVED);
+ }
continue;
}
}
@@ -961,12 +1050,16 @@ public class SettingsBackupAgent extends BackupAgentHelper {
Log.d(TAG, "Restored font scale from: " + toRestore + " to " + value);
}
-
+ // TODO(b/379861078): Log metrics inside this method.
settingsHelper.restoreValue(this, cr, contentValues, destination, key, value,
mRestoredFromSdkInt);
Log.d(TAG, "Restored setting: " + destination + " : " + key + "=" + value);
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestored(finalSettingsKey, /* count= */ 1);
+ }
}
+
}
@@ -996,29 +1089,29 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
@VisibleForTesting
- SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) {
+ SettingsBackupAllowlist getBackupAllowlist(Uri contentUri) {
// Figure out the white list and redirects to the global table. We restore anything
// in either the backup allowlist or the legacy-restore allowlist for this table.
- String[] whitelist;
+ String[] allowlist;
Map<String, Validator> validators = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
- whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP,
+ allowlist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS,
DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
validators = SecureSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
- whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP,
+ allowlist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS);
validators = SystemSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
- whitelist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(),
+ allowlist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(),
Settings.Global.LEGACY_RESTORE_SETTINGS);
validators = GlobalSettingsValidators.VALIDATORS;
} else {
throw new IllegalArgumentException("Unknown URI: " + contentUri);
}
- return new SettingsBackupWhitelist(whitelist, validators);
+ return new SettingsBackupAllowlist(allowlist, validators);
}
private String[] getGlobalSettingsToBackup() {
@@ -1118,11 +1211,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
*
* @param cursor A cursor with settings data.
* @param settings The settings to extract.
+ * @param settingsKey The key of the settings to extract (eg system).
* @return The byte array of extracted values.
*/
- private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
+ private byte[] extractRelevantValues(
+ Cursor cursor, String[] settings, String settingsKey) {
if (!cursor.moveToFirst()) {
Log.e(TAG, "Couldn't read from the cursor");
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger
+ .logItemsBackupFailed(
+ settingsKey,
+ settings.length,
+ ERROR_COULD_NOT_READ_FROM_CURSOR);
+ }
return new byte[0];
}
@@ -1181,6 +1283,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
}
+ if (areAgentMetricsEnabled) {
+ numberOfSettingsPerKey.put(settingsKey, backedUpSettingIndex);
+ }
+
// Aggregate the result.
byte[] result = new byte[totalSize];
int pos = 0;
@@ -1364,7 +1470,9 @@ public class SettingsBackupAgent extends BackupAgentHelper {
getContentResolver()
.query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
return extractRelevantValues(
- cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ cursor,
+ DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP,
+ KEY_DEVICE_SPECIFIC_CONFIG);
}
}
@@ -1399,7 +1507,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
null,
blockedSettingsArrayId,
dynamicBlocklist,
- preservedSettings);
+ preservedSettings,
+ KEY_DEVICE_SPECIFIC_CONFIG);
updateWindowManagerIfNeeded(originalDensity);
@@ -1597,14 +1706,14 @@ public class SettingsBackupAgent extends BackupAgentHelper {
* Store the allowlist of settings to be backed up and validators for them.
*/
@VisibleForTesting
- static class SettingsBackupWhitelist {
- final String[] mSettingsWhitelist;
+ static class SettingsBackupAllowlist {
+ final String[] mSettingsAllowlist;
final Map<String, Validator> mSettingsValidators;
- SettingsBackupWhitelist(String[] settingsWhitelist,
+ SettingsBackupAllowlist(String[] settingsAllowlist,
Map<String, Validator> settingsValidators) {
- mSettingsWhitelist = settingsWhitelist;
+ mSettingsAllowlist = settingsAllowlist;
mSettingsValidators = settingsValidators;
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index ebeee8564d2f..ea8ae7b208cd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -242,8 +242,7 @@ public class SettingsHelper {
// Don't write it to setting. Let the broadcast receiver in
// AccessibilityManagerService handle restore/merging logic.
return;
- } else if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()
- && Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) {
+ } else if (Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) {
// Don't write it to setting. Let the broadcast receiver in
// AccessibilityManagerService handle restore/merging logic.
return;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5ae11bacb445..37eda3ebf9a8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -3049,6 +3049,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.TOUCHPAD_THREE_FINGER_TAP_CUSTOMIZATION,
SystemSettingsProto.Touchpad.THREE_FINGER_TAP_CUSTOMIZATION);
+ dumpSetting(s, p,
+ Settings.System.TOUCHPAD_SYSTEM_GESTURES,
+ SystemSettingsProto.Touchpad.SYSTEM_GESTURES);
p.end(touchpadToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 011ffbc97d19..7aed61533aac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -178,11 +178,6 @@ final class SettingsState {
private static final String APEX_DIR = "/apex";
private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
- private static final String STORAGE_MIGRATION_FLAG =
- "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
- private static final String STORAGE_MIGRATION_MARKER_FILE =
- "/metadata/aconfig_test_missions/mission_1";
-
/**
* This tag is applied to all aconfig default value-loaded flags.
*/
@@ -414,6 +409,11 @@ final class SettingsState {
Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
}
}
+
+ if (Flags.disableBulkCompare()) {
+ return;
+ }
+
// TOBO(b/312444587): remove the comparison logic after Test Mission 2.
if (requests == null) {
Map<String, AconfigdFlagInfo> aconfigdFlagMap =
@@ -426,7 +426,7 @@ final class SettingsState {
}
}
- // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
+ // TODO(b/312444587): remove the comparison logic after Test Mission 2.
public int compareFlagValueInNewStorage(
Map<String, AconfigdFlagInfo> defaultFlagMap,
Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
@@ -1753,32 +1753,6 @@ final class SettingsState {
}
}
- if (isConfigSettingsKey(mKey) && name != null
- && name.equals(STORAGE_MIGRATION_FLAG)) {
- if (value.equals("true")) {
- Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
- if (!Files.exists(path)) {
- Files.createFile(path);
- }
-
- Set<PosixFilePermission> perms =
- Files.readAttributes(path, PosixFileAttributes.class).permissions();
- perms.add(PosixFilePermission.OWNER_WRITE);
- perms.add(PosixFilePermission.OWNER_READ);
- perms.add(PosixFilePermission.GROUP_READ);
- perms.add(PosixFilePermission.OTHERS_READ);
- try {
- Files.setPosixFilePermissions(path, perms);
- } catch (Exception e) {
- Slog.e(LOG_TAG, "failed to set permissions on migration marker", e);
- }
- } else {
- java.nio.file.Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
- if (Files.exists(path)) {
- Files.delete(path);
- }
- }
- }
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
fromSystem, Long.valueOf(id), isPreservedInRestore));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index aca26ecce29a..cfd27c69032e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -101,3 +101,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "disable_bulk_compare"
+ namespace: "core_experiments_team_internal"
+ description: "Disable bulk comparison between DeviceConfig and aconfig storage."
+ bug: "312444587"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a62b7fd3db81..9004488c2e12 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -947,7 +947,9 @@ public class SettingsBackupTest {
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
Settings.System.WEAR_TTS_PREWARM_ENABLED,
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
- Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific
+ // Potentially disruptive to on-boarding flow on new devices
+ Settings.System.TOUCHPAD_SYSTEM_GESTURES
);
if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
settings.add(Settings.System.MIN_REFRESH_RATE);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 3a39150523ac..350c149f40de 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -17,11 +17,23 @@
package com.android.providers.settings;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertArrayEquals;
-
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -32,7 +44,10 @@ import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.settings.validators.SettingsValidators;
import android.provider.settings.validators.Validator;
@@ -43,9 +58,14 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.window.flags.Flags;
+import java.util.List;
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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -54,12 +74,14 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
+
/**
* Tests for the SettingsHelperTest
* Usage: atest SettingsProviderTest:SettingsBackupAgentTest
@@ -73,6 +95,17 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>();
private static final Map<String, String> TEST_VALUES = new HashMap<>();
private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>();
+ private static final String TEST_KEY = "test_key";
+ private static final String TEST_VALUE = "test_value";
+ private static final String ERROR_COULD_NOT_READ_ENTITY = "could_not_read_entity";
+ private static final String ERROR_SKIPPED_BY_SYSTEM = "skipped_by_system";
+ private static final String ERROR_SKIPPED_BY_BLOCKLIST =
+ "skipped_by_dynamic_blocklist";
+ private static final String ERROR_SKIPPED_PRESERVED = "skipped_preserved";
+ private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation";
+ private static final String KEY_SYSTEM = "system";
+ private static final String KEY_SECURE = "secure";
+ private static final String KEY_GLOBAL = "global";
static {
DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -86,6 +119,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR);
}
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock private BackupDataInput mBackupDataInput;
+ @Mock private BackupDataOutput mBackupDataOutput;
+
private TestFriendlySettingsBackupAgent mAgentUnderTest;
private Context mContext;
@@ -203,19 +244,32 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
@Test
public void testOnRestore_preservedSettingsAreNotRestored() {
- SettingsBackupAgent.SettingsBackupWhitelist whitelist =
- new SettingsBackupAgent.SettingsBackupWhitelist(
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING },
TEST_VALUES_VALIDATORS);
- mAgentUnderTest.setSettingsWhitelist(whitelist);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
mAgentUnderTest.setBlockedSettings();
TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
mAgentUnderTest.mSettingsHelper = settingsHelper;
byte[] backupData = generateBackupData(TEST_VALUES);
- mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI,
- null, null, null, /* blockedSettingsArrayId */ 0, Collections.emptySet(),
- new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI))));
+ mAgentUnderTest.restoreSettings(
+ backupData,
+ /* pos */ 0,
+ backupData.length,
+ TEST_URI,
+ null,
+ null,
+ null,
+ /* blockedSettingsArrayId */ 0,
+ Collections.emptySet(),
+ new HashSet<>(Collections
+ .singletonList(
+ SettingsBackupAgent
+ .getQualifiedKeyForSetting(
+ PRESERVED_TEST_SETTING, TEST_URI))),
+ TEST_KEY);
assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING));
assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING));
@@ -262,6 +316,486 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
assertEquals("1.5", testedMethod.apply("1.8"));
}
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void onCreate_metricsFlagIsDisabled_areAgentMetricsEnabledIsFalse() {
+ mAgentUnderTest.onCreate();
+
+ assertFalse(mAgentUnderTest.areAgentMetricsEnabled);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void onCreate_flagIsEnabled_areAgentMetricsEnabledIsTrue() {
+ mAgentUnderTest.onCreate();
+
+ assertTrue(mAgentUnderTest.areAgentMetricsEnabled);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_dataWriteSucceeds_logsSuccessMetrics()
+ throws IOException {
+ when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+ when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+ mAgentUnderTest.writeDataForKey(
+ TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityHeaderFails_logsFailureMetrics()
+ throws IOException {
+ when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenThrow(new IOException());
+ when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+ mAgentUnderTest.writeDataForKey(
+ TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityDataFails_logsFailureMetrics()
+ throws IOException {
+ when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+ when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenThrow(new IOException());
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+ mAgentUnderTest.writeDataForKey(
+ TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void writeDataForKey_metricsFlagIsDisabled_doesNotLogMetrics()
+ throws IOException {
+ when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+ when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);
+
+ mAgentUnderTest.writeDataForKey(
+ TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyDoesNotContainKey_doesNotLogMetrics()
+ throws IOException {
+ when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
+ when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+
+ mAgentUnderTest.writeDataForKey(
+ TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);
+
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_agentMetricsAreLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreDisabled_agentMetricsAreNotLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNull(loggingResult);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_readEntityDataFails_failureIsLogged()
+ throws IOException {
+ when(mBackupDataInput.readEntityData(any(byte[].class), anyInt(), anyInt()))
+ .thenThrow(new IOException());
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+
+ mAgentUnderTest.restoreSettings(
+ mBackupDataInput,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ assertTrue(loggingResult.getErrors().containsKey(ERROR_COULD_NOT_READ_ENTITY));
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreDisabled_readEntityDataFails_failureIsNotLogged()
+ throws IOException {
+ when(mBackupDataInput.readEntityData(any(byte[].class), anyInt(), anyInt()))
+ .thenThrow(new IOException());
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+
+ mAgentUnderTest.restoreSettings(
+ mBackupDataInput,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsSkippedBySystem_failureIsLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ String[] settingBlockedBySystem = new String[] {OVERRIDDEN_TEST_SETTING};
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ settingBlockedBySystem,
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings(settingBlockedBySystem);
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_BY_SYSTEM));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsSkippedByBlockList_failureIsLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+ Set<String> dynamicBlockList =
+ Set.of(Uri.withAppendedPath(TEST_URI, OVERRIDDEN_TEST_SETTING).toString());
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ dynamicBlockList,
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_BY_BLOCKLIST));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsPreserved_failureIsLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+ Set<String> preservedSettings =
+ Set.of(Uri.withAppendedPath(TEST_URI, OVERRIDDEN_TEST_SETTING).toString());
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList = */ Collections.emptySet(),
+ preservedSettings,
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_PRESERVED));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsNotValid_failureIsLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ /* settingsValidators= */ null);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList = */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ assertTrue(loggingResult.getErrors().containsKey(ERROR_DID_NOT_PASS_VALIDATION));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToGlobal_agentMetricsAreLoggedWithGlobalKey() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ Set.of(OVERRIDDEN_TEST_SETTING),
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_GLOBAL, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToSecure_agentMetricsAreLoggedWithSecureKey() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ Set.of(OVERRIDDEN_TEST_SETTING),
+ /* movedToSystem= */ null,
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_SECURE, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToSystem_agentMetricsAreLoggedWithSystemKey() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SettingsBackupAgent.SettingsBackupAllowlist allowlist =
+ new SettingsBackupAgent.SettingsBackupAllowlist(
+ new String[] {OVERRIDDEN_TEST_SETTING},
+ TEST_VALUES_VALIDATORS);
+ mAgentUnderTest.setSettingsAllowlist(allowlist);
+ mAgentUnderTest.setBlockedSettings();
+ TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = settingsHelper;
+
+ byte[] backupData = generateBackupData(TEST_VALUES);
+ mAgentUnderTest
+ .restoreSettings(
+ backupData,
+ /* pos= */ 0,
+ backupData.length,
+ TEST_URI,
+ /* movedToGlobal= */ null,
+ /* movedToSecure= */ null,
+ /* movedToSystem= */ Set.of(OVERRIDDEN_TEST_SETTING),
+ /* blockedSettingsArrayId= */ 0,
+ /* dynamicBlockList= */ Collections.emptySet(),
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_SYSTEM, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
+ }
+
private byte[] generateBackupData(Map<String, String> keyValueData) {
int totalBytes = 0;
for (String key : keyValueData.keySet()) {
@@ -293,7 +827,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
null,
R.array.restore_blocked_global_settings,
/* dynamicBlockList= */ Collections.emptySet(),
- /* settingsToPreserve= */ Collections.emptySet());
+ /* settingsToPreserve= */ Collections.emptySet(),
+ TEST_KEY);
}
private byte[] generateUncorruptedHeader() throws IOException {
@@ -329,6 +864,21 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
}
}
+ private DataTypeResult getLoggingResultForDatatype(
+ String dataType, SettingsBackupAgent agent) {
+ if (agent.getBackupRestoreEventLogger() == null) {
+ return null;
+ }
+ List<DataTypeResult> loggingResults =
+ agent.getBackupRestoreEventLogger().getLoggingResults();
+ for (DataTypeResult result : loggingResults) {
+ if (result.getDataType().equals(dataType)) {
+ return result;
+ }
+ }
+ return null;
+ }
+
private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
os.write(SettingsBackupAgent.toByteArray(key));
@@ -340,7 +890,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
private Boolean mForcedDeviceInfoRestoreAcceptability = null;
private String[] mBlockedSettings = null;
- private SettingsBackupWhitelist mSettingsWhitelist = null;
+ private SettingsBackupAllowlist mSettingsAllowlist = null;
void setForcedDeviceInfoRestoreAcceptability(boolean value) {
mForcedDeviceInfoRestoreAcceptability = value;
@@ -350,8 +900,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
mBlockedSettings = blockedSettings;
}
- void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) {
- mSettingsWhitelist = settingsWhitelist;
+ void setSettingsAllowlist(SettingsBackupAllowlist settingsAllowlist) {
+ mSettingsAllowlist = settingsAllowlist;
}
@Override
@@ -369,12 +919,18 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
}
@Override
- SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) {
- if (mSettingsWhitelist == null) {
- return super.getBackupWhitelist(contentUri);
+ SettingsBackupAllowlist getBackupAllowlist(Uri contentUri) {
+ if (mSettingsAllowlist == null) {
+ return super.getBackupAllowlist(contentUri);
}
- return mSettingsWhitelist;
+ return mSettingsAllowlist;
+ }
+
+ void setNumberOfSettingsPerKey(String key, int numberOfSettings) {
+ if (numberOfSettingsPerKey != null) {
+ this.numberOfSettingsPerKey.put(key, numberOfSettings);
+ }
}
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index f64f72a74609..048d93b09967 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -26,8 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.SettingsStringUtil;
@@ -37,7 +35,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -52,9 +49,6 @@ import java.util.concurrent.ExecutionException;
@RunWith(AndroidJUnit4.class)
public class SettingsHelperRestoreTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
private static final float FLOAT_TOLERANCE = 0.01f;
private Context mContext;
@@ -211,7 +205,6 @@ public class SettingsHelperRestoreTest {
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
public void restoreAccessibilityShortcutTargetService_broadcastSent()
throws ExecutionException, InterruptedException {
BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 48ce49dbfb3a..276b206cd6a1 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -27,6 +27,7 @@ import android.aconfig.Aconfig.parsed_flags;
import android.aconfigd.AconfigdFlagInfo;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Xml;
@@ -1304,6 +1305,7 @@ public class SettingsStateTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE)
public void testCompareFlagValueInNewStorage() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
index 5f810858b7cd..5fdf0451d2c8 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -12,7 +12,10 @@ shell_srcs = [
"src/**/*.java",
":dumpstate_aidl",
]
-shell_static_libs = ["androidx.legacy_legacy-support-v4"]
+shell_static_libs = [
+ "androidx.legacy_legacy-support-v4",
+ "wear_aconfig_declarations_flags_java_lib",
+]
android_app {
name: "Shell",
@@ -28,6 +31,7 @@ android_app {
flags_packages: [
"android.security.flags-aconfig",
"android.permission.flags-aconfig",
+ "wear_aconfig_declarations",
],
platform_apis: true,
certificate: "platform",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0ec5571a7b8f..baf829ab3b14 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,7 +65,6 @@
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
- <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
@@ -741,6 +740,9 @@
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" />
+ <!-- Permission required to access bugreport and screenshot files created by wear. -->
+ <uses-permission android:name="com.google.wear.permission.ACCESS_BUG_REPORT_FILES" />
+
<!-- Permission required to run GtsAssistantTestCases -->
<uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
diff --git a/packages/Shell/res/values/defaults.xml b/packages/Shell/res/values/defaults.xml
new file mode 100644
index 000000000000..b693cc826be0
--- /dev/null
+++ b/packages/Shell/res/values/defaults.xml
@@ -0,0 +1,5 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Default for Wear bugreport warning activity-->
+ <!-- DO NOT TRANSLATE -->
+ <string name="system_ui_wear_bugreport_warning_activity" />
+</resources> \ No newline at end of file
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7f25b51e35ca..0694b6123c11 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -24,6 +24,7 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.shell.BugreportPrefs.STATE_HIDE;
import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.flags.Flags.handleBugreportsForWear;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -89,10 +90,10 @@ import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.google.android.collect.Lists;
-
import libcore.io.Streams;
+import com.google.android.collect.Lists;
+
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -140,6 +141,7 @@ import java.util.zip.ZipOutputStream;
public class BugreportProgressService extends Service {
private static final String TAG = "BugreportProgressService";
private static final boolean DEBUG = false;
+ private static final String WRITE_AND_APPEND_MODE = "wa";
private Intent startSelfIntent;
@@ -384,7 +386,11 @@ public class BugreportProgressService extends Service {
}
private static String getFileName(BugreportInfo info, String suffix) {
- return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
+ return getFileName(suffix, info.baseName, info.getName());
+ }
+
+ private static String getFileName(String suffix, String baseName, String name) {
+ return String.format("%s-%s%s", baseName, name, suffix);
}
private final class BugreportCallbackImpl extends BugreportCallback {
@@ -420,14 +426,14 @@ public class BugreportProgressService extends Service {
@Override
public void onFinished() {
- mInfo.renameBugreportFile();
- mInfo.renameScreenshots();
- if (mInfo.bugreportFile.length() == 0) {
- Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportFile);
- onError(BUGREPORT_ERROR_RUNTIME);
- return;
- }
synchronized (mLock) {
+ mInfo.renameBugreportFile();
+ mInfo.renameScreenshots();
+ if (mInfo.bugreportLocationInfo.isFileEmpty(mContext)) {
+ Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportLocationInfo);
+ onError(BUGREPORT_ERROR_RUNTIME);
+ return;
+ }
sendBugreportFinishedBroadcastLocked();
mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo));
}
@@ -454,15 +460,15 @@ public class BugreportProgressService extends Service {
@GuardedBy("mLock")
private void sendBugreportFinishedBroadcastLocked() {
- final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath();
- if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
- sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
- mInfo.bugreportFile, mInfo.nonce);
+ File bugreportFile = mInfo.bugreportLocationInfo.mBugreportFile;
+ if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE && bugreportFile != null) {
+ sendRemoteBugreportFinishedBroadcast(
+ mContext, bugreportFile.getAbsolutePath(), bugreportFile, mInfo.nonce);
} else {
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
- intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
- intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
+ intent.putExtra(EXTRA_BUGREPORT, mInfo.bugreportLocationInfo.getBugreportPath());
+ intent.putExtra(EXTRA_SCREENSHOT, mInfo.screenshotLocationInfo.getScreenshotPath());
mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
onBugreportFinished(mInfo);
}
@@ -498,19 +504,6 @@ public class BugreportProgressService extends Service {
android.Manifest.permission.DUMP);
}
- /**
- * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
- * screenshot is the default screenshot for the bugreport types that take it.
- */
- private static String getScreenshotForIntent(BugreportInfo info) {
- if (!info.screenshotFiles.isEmpty()) {
- final File screenshotFile = info.screenshotFiles.get(0);
- final String screenshotFilePath = screenshotFile.getAbsolutePath();
- return screenshotFilePath;
- }
- return null;
- }
-
private static String generateFileHash(String fileName) {
String fileHash = null;
try {
@@ -715,24 +708,28 @@ public class BugreportProgressService extends Service {
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
List<Uri> extraAttachments =
intent.getParcelableArrayListExtra(EXTRA_EXTRA_ATTACHMENT_URIS, Uri.class);
-
- BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
- shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachments);
- synchronized (mLock) {
- if (info.bugreportFile.exists()) {
- Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
- + info.bugreportFile + " already exists");
- return;
- }
- info.createBugreportFile();
+ BugreportInfo info =
+ setupFilesAndCreateBugreportInfo(
+ intent,
+ bugreportType,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ nonce,
+ extraAttachments);
+ if (info == null) {
+ Log.e(TAG, "Could not initialize bugreport inputs");
+ return;
}
+
ParcelFileDescriptor bugreportFd = info.getBugreportFd();
if (bugreportFd == null) {
Log.e(TAG, "Failed to start bugreport generation as "
+ " bugreport parcel file descriptor is null.");
return;
}
- info.createScreenshotFile(mBugreportsDir);
+
ParcelFileDescriptor screenshotFd = null;
if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) {
screenshotFd = info.getDefaultScreenshotFd();
@@ -740,7 +737,7 @@ public class BugreportProgressService extends Service {
Log.e(TAG, "Failed to start bugreport generation as"
+ " screenshot parcel file descriptor is null. Deleting bugreport file");
FileUtils.closeQuietly(bugreportFd);
- info.bugreportFile.delete();
+ info.bugreportLocationInfo.maybeDeleteBugreportFile();
return;
}
}
@@ -768,6 +765,56 @@ public class BugreportProgressService extends Service {
}
}
+ // Sets up BugreportInfo. If needed, creates bugreport and screenshot files.
+ private BugreportInfo setupFilesAndCreateBugreportInfo(
+ Intent intent,
+ int bugreportType,
+ String baseName,
+ String name,
+ String shareTitle,
+ String shareDescription,
+ long nonce,
+ List<Uri> extraAttachments) {
+ ArrayList<Uri> brAndScreenshot;
+ Uri bugReportUri = null;
+ Uri screenshotUri = null;
+
+ if (handleBugreportsForWear() && bugreportType == BugreportParams.BUGREPORT_MODE_WEAR) {
+ brAndScreenshot = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (brAndScreenshot != null && !brAndScreenshot.isEmpty()) {
+ bugReportUri = brAndScreenshot.get(0);
+ if (bugReportUri == null) {
+ Log.e(TAG, "Can't start bugreport request. Bugreport uri is null.");
+ return null;
+ }
+ screenshotUri = (brAndScreenshot.size() > 1) ? brAndScreenshot.get(1) : null;
+ }
+ }
+
+ BugreportLocationInfo bugreportLocationInfo =
+ new BugreportLocationInfo(bugReportUri, mBugreportsDir, baseName, name);
+ ScreenshotLocationInfo screenshotLocationInfo = new ScreenshotLocationInfo(screenshotUri);
+ BugreportInfo info =
+ new BugreportInfo(
+ mContext,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ bugreportType,
+ nonce,
+ extraAttachments,
+ bugreportLocationInfo,
+ screenshotLocationInfo);
+ synchronized (mLock) {
+ if (!bugreportLocationInfo.maybeCreateBugreportFile()) {
+ return null;
+ }
+ }
+ info.maybeCreateScreenshotFile(mBugreportsDir);
+ return info;
+ }
+
private static boolean isDefaultScreenshotRequired(
@BugreportParams.BugreportMode int bugreportType,
boolean hasScreenshotButton) {
@@ -1177,8 +1224,9 @@ public class BugreportProgressService extends Service {
stopForegroundWhenDoneLocked(info.id);
}
- if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
- Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ if (!info.bugreportLocationInfo.isValidBugreportResult()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
Toast.makeText(mContext, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
synchronized (mLock) {
stopProgressLocked(info.id);
@@ -1194,7 +1242,7 @@ public class BugreportProgressService extends Service {
* the bugreport.
*/
private void triggerLocalNotification(final BugreportInfo info) {
- boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
+ boolean isPlainText = info.bugreportLocationInfo.isPlainText();
if (!isPlainText) {
// Already zipped, send it right away.
sendBugreportNotification(info, mTakingScreenshot);
@@ -1223,11 +1271,11 @@ public class BugreportProgressService extends Service {
// grant temporary permissions for.
final Uri bugreportUri;
try {
- bugreportUri = getUri(context, info.bugreportFile);
+ bugreportUri = getUri(context, info.bugreportLocationInfo.mBugreportFile);
} catch (IllegalArgumentException e) {
// Should not happen on production, but happens when a Shell is sideloaded and
// FileProvider cannot find a configured root for it.
- Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e);
+ Log.wtf(TAG, "Could not get URI for " + info.bugreportLocationInfo.mBugreportFile, e);
return null;
}
@@ -1258,7 +1306,7 @@ public class BugreportProgressService extends Service {
new ClipData.Item(null, null, null, bugreportUri));
Log.d(TAG, "share intent: bureportUri=" + bugreportUri);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- for (File screenshot : info.screenshotFiles) {
+ for (File screenshot : info.screenshotLocationInfo.mScreenshotFiles) {
final Uri screenshotUri = getUri(context, screenshot);
Log.d(TAG, "share intent: screenshotUri=" + screenshotUri);
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
@@ -1317,7 +1365,11 @@ public class BugreportProgressService extends Service {
*/
private Intent buildWearWarningIntent() {
Intent intent = new Intent();
- intent.setClassName(mContext, getPackageName() + ".WearBugreportWarningActivity");
+ String systemUIPackage = mContext.getResources().getString(
+ com.android.internal.R.string.config_systemUi);
+ String wearBugreportWarningActivity = getResources()
+ .getString(R.string.system_ui_wear_bugreport_warning_activity);
+ intent.setClassName(systemUIPackage, wearBugreportWarningActivity);
if (mContext.getPackageManager().resolveActivity(intent, /* flags */ 0) == null) {
Log.e(TAG, "Cannot find wear bugreport warning activity");
return buildWarningIntent(mContext, /* sendIntent */ null);
@@ -1512,22 +1564,25 @@ public class BugreportProgressService extends Service {
* original in case of failure).
*/
private static void zipBugreport(BugreportInfo info) {
- final String bugreportPath = info.bugreportFile.getAbsolutePath();
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final String bugreportPath = bugreportFile.getAbsolutePath();
final String zippedPath = bugreportPath.replace(".txt", ".zip");
Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
final File bugreportZippedFile = new File(zippedPath);
- try (InputStream is = new FileInputStream(info.bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
- addEntry(zos, info.bugreportFile.getName(), is);
+ try (InputStream is = new FileInputStream(bugreportFile);
+ ZipOutputStream zos =
+ new ZipOutputStream(
+ new BufferedOutputStream(
+ new FileOutputStream(bugreportZippedFile)))) {
+ addEntry(zos, bugreportFile.getName(), is);
// Delete old file
- final boolean deleted = info.bugreportFile.delete();
+ final boolean deleted = bugreportFile.delete();
if (deleted) {
Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
} else {
Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
}
- info.bugreportFile = bugreportZippedFile;
+ info.bugreportLocationInfo.mBugreportFile = bugreportZippedFile;
} catch (IOException e) {
Log.e(TAG, "exception zipping file " + zippedPath, e);
}
@@ -1557,7 +1612,11 @@ public class BugreportProgressService extends Service {
@GuardedBy("mLock")
private void addDetailsToZipFileLocked(BugreportInfo info) {
- if (info.bugreportFile == null) {
+ if (handleBugreportsForWear()) {
+ Log.d(TAG, "Skipping adding details to zipped file");
+ return;
+ }
+ if (info.bugreportLocationInfo.mBugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
return;
@@ -1588,10 +1647,11 @@ public class BugreportProgressService extends Service {
sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
}
- final File dir = info.bugreportFile.getParentFile();
- final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final File dir = bugreportFile.getParentFile();
+ final File tmpZip = new File(dir, "tmp-" + bugreportFile.getName());
Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
- try (ZipFile oldZip = new ZipFile(info.bugreportFile);
+ try (ZipFile oldZip = new ZipFile(bugreportFile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
// First copy contents from original zip.
@@ -1628,8 +1688,8 @@ public class BugreportProgressService extends Service {
stopForegroundWhenDoneLocked(info.id);
}
- if (!tmpZip.renameTo(info.bugreportFile)) {
- Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
+ if (!tmpZip.renameTo(bugreportFile)) {
+ Log.e(TAG, "Could not rename " + tmpZip + " to " + bugreportFile);
}
}
@@ -2087,15 +2147,9 @@ public class BugreportProgressService extends Service {
*/
String formattedLastUpdate;
- /**
- * Path of the main bugreport file.
- */
- File bugreportFile;
+ BugreportLocationInfo bugreportLocationInfo;
- /**
- * Path of the screenshot files.
- */
- List<File> screenshotFiles = new ArrayList<>(1);
+ ScreenshotLocationInfo screenshotLocationInfo;
/**
* Whether dumpstate sent an intent informing it has finished.
@@ -2138,10 +2192,17 @@ public class BugreportProgressService extends Service {
/**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, String baseName, String name,
- @Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
- @Nullable List<Uri> extraAttachments) {
+ BugreportInfo(
+ Context context,
+ String baseName,
+ String name,
+ @Nullable String shareTitle,
+ @Nullable String shareDescription,
+ @BugreportParams.BugreportMode int type,
+ long nonce,
+ @Nullable List<Uri> extraAttachments,
+ BugreportLocationInfo bugreportLocationInfo,
+ ScreenshotLocationInfo screenshotLocationInfo) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2149,29 +2210,27 @@ public class BugreportProgressService extends Service {
this.type = type;
this.nonce = nonce;
this.baseName = baseName;
- this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.bugreportLocationInfo = bugreportLocationInfo;
+ this.screenshotLocationInfo = screenshotLocationInfo;
this.extraAttachments = extraAttachments;
}
- void createBugreportFile() {
- createReadWriteFile(bugreportFile);
- }
-
- void createScreenshotFile(File bugreportsDir) {
+ void maybeCreateScreenshotFile(File bugreportsDir) {
+ if (screenshotLocationInfo.mScreenshotUri != null) {
+ // Screenshot file was already created.
+ return;
+ }
File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
addScreenshot(screenshotFile);
createReadWriteFile(screenshotFile);
}
ParcelFileDescriptor getBugreportFd() {
- return getFd(bugreportFile);
+ return bugreportLocationInfo.getBugreportFd(context);
}
ParcelFileDescriptor getDefaultScreenshotFd() {
- if (screenshotFiles.isEmpty()) {
- return null;
- }
- return getFd(screenshotFiles.get(0));
+ return screenshotLocationInfo.getScreenshotFd(context);
}
void setTitle(String title) {
@@ -2229,14 +2288,14 @@ public class BugreportProgressService extends Service {
* Saves the location of a taken screenshot so it can be sent out at the end.
*/
void addScreenshot(File screenshot) {
- screenshotFiles.add(screenshot);
+ screenshotLocationInfo.mScreenshotFiles.add(screenshot);
}
/**
* Deletes all screenshots taken for a given bugreport.
*/
private void deleteScreenshots() {
- for (File file : screenshotFiles) {
+ for (File file : screenshotLocationInfo.mScreenshotFiles) {
Log.i(TAG, "Deleting screenshot file " + file);
file.delete();
}
@@ -2246,18 +2305,14 @@ public class BugreportProgressService extends Service {
* Deletes bugreport file for a given bugreport.
*/
private void deleteBugreportFile() {
- Log.i(TAG, "Deleting bugreport file " + bugreportFile);
- bugreportFile.delete();
+ bugreportLocationInfo.maybeDeleteBugreportFile();
}
/**
* Deletes empty files for a given bugreport.
*/
private void deleteEmptyFiles() {
- if (bugreportFile.length() == 0) {
- Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
- bugreportFile.delete();
- }
+ bugreportLocationInfo.maybeDeleteEmptyBugreport();
deleteEmptyScreenshots();
}
@@ -2265,14 +2320,7 @@ public class BugreportProgressService extends Service {
* Deletes empty screenshot files.
*/
private void deleteEmptyScreenshots() {
- screenshotFiles.removeIf(file -> {
- final long length = file.length();
- if (length == 0) {
- Log.i(TAG, "Deleting empty screenshot file: " + file);
- file.delete();
- }
- return length == 0;
- });
+ screenshotLocationInfo.deleteEmptyScreenshots();
}
/**
@@ -2280,43 +2328,14 @@ public class BugreportProgressService extends Service {
* {@code initialName} if user has changed it.
*/
void renameScreenshots() {
- deleteEmptyScreenshots();
- if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) {
- return;
- }
- final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
- for (File oldFile : screenshotFiles) {
- final String oldName = oldFile.getName();
- final String newName = oldName.replaceFirst(initialName, name);
- final File newFile;
- if (!newName.equals(oldName)) {
- final File renamedFile = new File(oldFile.getParentFile(), newName);
- Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
- newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
- } else {
- Log.w(TAG, "Name didn't change: " + oldName);
- newFile = oldFile;
- }
- if (newFile.length() > 0) {
- renamedFiles.add(newFile);
- } else if (newFile.delete()) {
- Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
- }
- }
- screenshotFiles = renamedFiles;
+ screenshotLocationInfo.renameScreenshots(initialName, name);
}
/**
* Rename bugreport file to include the name given by user via UI
*/
void renameBugreportFile() {
- File newBugreportFile = new File(bugreportFile.getParentFile(),
- getFileName(this, ".zip"));
- if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
- if (bugreportFile.renameTo(newBugreportFile)) {
- bugreportFile = newBugreportFile;
- }
- }
+ bugreportLocationInfo.maybeRenameBugreportFile(this);
}
String getFormattedLastUpdate() {
@@ -2349,16 +2368,23 @@ public class BugreportProgressService extends Service {
builder.append("(").append(description.length()).append(" chars)");
}
- return builder
- .append("\n\tfile: ").append(bugreportFile)
- .append("\n\tscreenshots: ").append(screenshotFiles)
- .append("\n\tprogress: ").append(progress)
- .append("\n\tlast_update: ").append(getFormattedLastUpdate())
- .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
- .append(" addedDetailsToZip: ").append(addedDetailsToZip)
- .append("\n\tshareDescription: ").append(shareDescription)
- .append("\n\tshareTitle: ").append(shareTitle)
- .toString();
+ return builder.append("\n\tfile: ")
+ .append(bugreportLocationInfo)
+ .append("\n\tscreenshots: ")
+ .append(screenshotLocationInfo)
+ .append("\n\tprogress: ")
+ .append(progress)
+ .append("\n\tlast_update: ")
+ .append(getFormattedLastUpdate())
+ .append("\n\taddingDetailsToZip: ")
+ .append(addingDetailsToZip)
+ .append(" addedDetailsToZip: ")
+ .append(addedDetailsToZip)
+ .append("\n\tshareDescription: ")
+ .append(shareDescription)
+ .append("\n\tshareTitle: ")
+ .append(shareTitle)
+ .toString();
}
// Parcelable contract
@@ -2375,11 +2401,12 @@ public class BugreportProgressService extends Service {
lastProgress.set(in.readInt());
lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
- bugreportFile = readFile(in);
+ bugreportLocationInfo = new BugreportLocationInfo(readFile(in));
int screenshotSize = in.readInt();
for (int i = 1; i <= screenshotSize; i++) {
- screenshotFiles.add(readFile(in));
+ screenshotLocationInfo = new ScreenshotLocationInfo(null);
+ screenshotLocationInfo.mScreenshotFiles.add(readFile(in));
}
finished.set(in.readInt() == 1);
@@ -2404,10 +2431,10 @@ public class BugreportProgressService extends Service {
dest.writeInt(lastProgress.intValue());
dest.writeLong(lastUpdate.longValue());
dest.writeString(getFormattedLastUpdate());
- writeFile(dest, bugreportFile);
+ writeFile(dest, bugreportLocationInfo.mBugreportFile);
- dest.writeInt(screenshotFiles.size());
- for (File screenshotFile : screenshotFiles) {
+ dest.writeInt(screenshotLocationInfo.mScreenshotFiles.size());
+ for (File screenshotFile : screenshotLocationInfo.mScreenshotFiles) {
writeFile(dest, screenshotFile);
}
@@ -2449,6 +2476,261 @@ public class BugreportProgressService extends Service {
};
}
+ /**
+ * Class for abstracting bugreport location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create a bugreport file and
+ * manage its lifecycle.
+ */
+ private static final class BugreportLocationInfo {
+ /** Path of the main bugreport file. */
+ @Nullable private File mBugreportFile;
+
+ /** Uri to bugreport location. */
+ @Nullable private Uri mBugreportUri;
+
+ BugreportLocationInfo(File bugreportFile) {
+ this.mBugreportFile = bugreportFile;
+ }
+
+ BugreportLocationInfo(Uri bugreportUri, File bugreportsDir, String baseName, String name) {
+ if (bugreportUri != null) {
+ this.mBugreportUri = bugreportUri;
+ } else {
+ this.mBugreportFile = new File(bugreportsDir, getFileName(".zip", baseName, name));
+ }
+ }
+
+ private boolean maybeCreateBugreportFile() {
+ if (mBugreportFile != null && mBugreportFile.exists()) {
+ Log.e(
+ TAG,
+ "Failed to start bugreport generation, the requested bugreport file "
+ + mBugreportFile
+ + " already exists");
+ return false;
+ }
+ createBugreportFile();
+ return true;
+ }
+
+ private void createBugreportFile() {
+ if (mBugreportUri == null) {
+ createReadWriteFile(mBugreportFile);
+ }
+ }
+
+ private ParcelFileDescriptor getBugreportFd(Context context) {
+ if (mBugreportUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mBugreportUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting BR file descriptor", e);
+ return null;
+ }
+ }
+ if (mBugreportFile == null) {
+ Log.e(TAG, "Could not get bugreport file descriptor; bugreport file was null");
+ return null;
+ }
+ return getFd(mBugreportFile);
+ }
+
+ private void maybeDeleteBugreportFile() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ Log.i(TAG, "Deleting bugreport file " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+
+ private boolean isValidBugreportResult() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.exists() && mBugreportFile.canRead();
+ }
+ // If a bugreport uri was provided, we can't assert on whether the file exists and can
+ // be read. Assume the result is valid.
+ return true;
+ }
+
+ private void maybeDeleteEmptyBugreport() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ if (mBugreportFile.length() == 0) {
+ Log.i(TAG, "Deleting empty bugreport file: " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+ }
+
+ private void maybeRenameBugreportFile(BugreportInfo bugreportInfo) {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's naming.
+ return;
+ }
+ File newBugreportFile =
+ new File(mBugreportFile.getParentFile(), getFileName(bugreportInfo, ".zip"));
+ if (!newBugreportFile.getPath().equals(mBugreportFile.getPath())) {
+ if (mBugreportFile.renameTo(newBugreportFile)) {
+ mBugreportFile = newBugreportFile;
+ }
+ }
+ }
+
+ private boolean isPlainText() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.getName().toLowerCase().endsWith(".txt");
+ }
+ return false;
+ }
+
+ private boolean isFileEmpty(Context context) {
+ if (mBugreportFile != null) {
+ return mBugreportFile.length() == 0;
+ }
+ return getBugreportFd(context).getStatSize() == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BugreportLocationInfo{"
+ + "bugreportFile="
+ + mBugreportFile
+ + ", bugreportUri="
+ + mBugreportUri
+ + '}';
+ }
+
+ private String getBugreportPath() {
+ if (mBugreportUri != null) {
+ return mBugreportUri.getLastPathSegment();
+ }
+ return mBugreportFile.getAbsolutePath();
+ }
+ }
+
+ /**
+ * Class for abstracting screenshot location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create the screenshot file and
+ * manage its lifecycle.
+ */
+ private static final class ScreenshotLocationInfo {
+
+ /** Uri to screenshot location. */
+ @Nullable private Uri mScreenshotUri;
+
+ /** Path to screenshot files. */
+ private List<File> mScreenshotFiles = new ArrayList<>(1);
+
+ ScreenshotLocationInfo(Uri screenshotUri) {
+ if (screenshotUri != null) {
+ this.mScreenshotUri = screenshotUri;
+ }
+ }
+
+ private ParcelFileDescriptor getScreenshotFd(Context context) {
+ if (mScreenshotUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mScreenshotUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting screenshot file", e);
+ return null;
+ }
+ }
+
+ if (mScreenshotFiles.isEmpty()) {
+ return null;
+ }
+ return getFd(mScreenshotFiles.getFirst());
+ }
+
+ @Override
+ public String toString() {
+ return "ScreenshotLocationInfo{"
+ + "screenshotUri="
+ + mScreenshotUri
+ + ", screenshotFiles="
+ + mScreenshotFiles
+ + '}';
+ }
+
+ private String getScreenshotPath() {
+ if (mScreenshotUri != null) {
+ return mScreenshotUri.getLastPathSegment();
+ }
+ return getScreenshotForIntent();
+ }
+
+ private void renameScreenshots(String initialName, String name) {
+ if (mScreenshotUri != null) {
+ // If a screenshot uri is provided, then shell is not responsible for the
+ // screenshot's naming.
+ return;
+ }
+ deleteEmptyScreenshots();
+ if (TextUtils.isEmpty(name) || mScreenshotFiles.isEmpty()) {
+ // If there is no user set name for screenshot file or there are no screenshot
+ // files, there's nothing to do.
+ return;
+ }
+ final List<File> renamedFiles = new ArrayList<>(mScreenshotFiles.size());
+ for (File oldFile : mScreenshotFiles) {
+ final String oldName = oldFile.getName();
+ final String newName = oldName.replaceFirst(initialName, name);
+ final File newFile;
+ if (!newName.equals(oldName)) {
+ final File renamedFile = new File(oldFile.getParentFile(), newName);
+ Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
+ newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
+ } else {
+ Log.w(TAG, "Name didn't change: " + oldName);
+ newFile = oldFile;
+ }
+ if (newFile.length() > 0) {
+ renamedFiles.add(newFile);
+ } else if (newFile.delete()) {
+ Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
+ }
+ }
+ mScreenshotFiles = renamedFiles;
+ }
+
+ private void deleteEmptyScreenshots() {
+ mScreenshotFiles.removeIf(
+ file -> {
+ final long length = file.length();
+ if (length == 0) {
+ Log.i(TAG, "Deleting empty screenshot file: " + file);
+ file.delete();
+ }
+ return length == 0;
+ });
+ }
+
+ /**
+ * Checks if screenshot array is non-empty and returns the first screenshot's path. The
+ * first screenshot is the default screenshot for the bugreport types that take it.
+ */
+ private String getScreenshotForIntent() {
+ if (!mScreenshotFiles.isEmpty()) {
+ final File screenshotFile = mScreenshotFiles.getFirst();
+ return screenshotFile.getAbsolutePath();
+ }
+ return null;
+ }
+ }
+
@GuardedBy("mLock")
private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
if (progress > CAPPED_PROGRESS) {
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 86dce643de37..f78fe3cda816 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -25,5 +25,5 @@
<string name="delete_ringtone_text" msgid="201443984070732499">"Hapus"</string>
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
- <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+ <string name="app_label" msgid="3091611356093417332">"Suara"</string>
</resources>
diff --git a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
index 021a5143c09a..6af8004c4015 100644
--- a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
+++ b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
@@ -30,6 +30,7 @@ class StatementServiceApplication : Application() {
// WorkManager can only schedule when the user data directories are unencrypted (after
// the user has entered their lock password.
DomainVerificationUtils.schedulePeriodicCheckUnlocked(WorkManager.getInstance(this))
+ DomainVerificationUtils.schedulePeriodicUpdateUnlocked(WorkManager.getInstance(this))
}
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
index 694424822b30..157a800d0e09 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
@@ -22,6 +22,7 @@ import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.android.statementservice.domain.worker.RetryRequestWorker
+import com.android.statementservice.domain.worker.UpdateVerifiedDomainsWorker
import java.time.Duration
object DomainVerificationUtils {
@@ -30,6 +31,10 @@ object DomainVerificationUtils {
private const val PERIODIC_SHORT_HOURS = 24L
private const val PERIODIC_LONG_ID = "retry_long"
private const val PERIODIC_LONG_HOURS = 72L
+ private const val PERIODIC_UPDATE_ID = "update"
+ private const val PERIODIC_UPDATE_HOURS = 720L
+
+ private const val UPDATE_WORKER_ENABLED = false
/**
* In a majority of cases, the initial requests will be enough to verify domains, since they
@@ -74,4 +79,38 @@ object DomainVerificationUtils {
}
}
}
+
+ /**
+ * Schedule a periodic worker to check for any updates to assetlink.json files for domains that
+ * have already been verified.
+ *
+ * Due to the potential for this worker to generate enough traffic across all android devices
+ * to overwhelm websites, this method is hardcoded to be disabled by default. It is highly
+ * recommended to not enable this worker and instead implement a custom worker that pulls
+ * updates from a caching service instead of directly from websites.
+ */
+ fun schedulePeriodicUpdateUnlocked(workManager: WorkManager) {
+ if (UPDATE_WORKER_ENABLED) {
+ workManager.apply {
+ PeriodicWorkRequestBuilder<UpdateVerifiedDomainsWorker>(
+ Duration.ofDays(
+ PERIODIC_UPDATE_HOURS
+ )
+ )
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .setRequiresDeviceIdle(true)
+ .build()
+ )
+ .build()
+ .let {
+ enqueueUniquePeriodicWork(
+ PERIODIC_UPDATE_ID,
+ ExistingPeriodicWorkPolicy.KEEP, it
+ )
+ }
+ }
+ }
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 6914347544de..c7f6c184fd22 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -64,7 +64,8 @@ class DomainVerifier private constructor(
private val targetAssetCache = AssetLruCache()
- fun collectHosts(packageNames: Iterable<String>): Iterable<Triple<UUID, String, String>> {
+ fun collectHosts(packageNames: Iterable<String>, statusFilter: (Int) -> Boolean):
+ Iterable<Triple<UUID, String, Iterable<String>>> {
return packageNames.mapNotNull { packageName ->
val (domainSetId, _, hostToStateMap) = try {
manager.getDomainVerificationInfo(packageName)
@@ -74,14 +75,13 @@ class DomainVerifier private constructor(
} ?: return@mapNotNull null
val hostsToRetry = hostToStateMap
- .filterValues(VerifyStatus::shouldRetry)
+ .filterValues(statusFilter)
.takeIf { it.isNotEmpty() }
?.map { it.key }
?: return@mapNotNull null
- hostsToRetry.map { Triple(domainSetId, packageName, it) }
+ Triple(domainSetId, packageName, hostsToRetry)
}
- .flatten()
}
suspend fun verifyHost(
diff --git a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
index 2193ec542238..c771da3ab563 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
@@ -49,7 +49,7 @@ enum class VerifyStatus(val value: Int) {
return false
}
- val status = values().find { it.value == state } ?: return true
+ val status = entries.find { it.value == state } ?: return true
return when (status) {
SUCCESS,
FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
@@ -62,5 +62,20 @@ enum class VerifyStatus(val value: Int) {
FAILURE_REDIRECT -> true
}
}
+
+ fun canUpdate(state: Int): Boolean {
+ if (state == DomainVerificationInfo.STATE_UNMODIFIABLE) {
+ return false
+ }
+
+ val status = entries.find { it.value == state }
+ return when (status) {
+ SUCCESS,
+ FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
+ FAILURE_REJECTED_BY_SERVER,
+ UNKNOWN -> true
+ else -> false
+ }
+ }
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt
new file mode 100644
index 000000000000..7ec6e6c28e9b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.statementservice.domain.worker
+
+import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
+import androidx.work.ListenableWorker
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
+
+abstract class PeriodicUpdateWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : BaseRequestWorker(appContext, params) {
+
+ data class VerifyResult(
+ val host: String,
+ val status: VerifyStatus,
+ val groups: List<UriRelativeFilterGroup>
+ )
+
+ protected suspend fun updateDomainVerificationStatus(verifyStatusFilter: (Int) -> Boolean):
+ ListenableWorker.Result {
+ return coroutineScope {
+ if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val packageNames = verificationManager.queryValidVerificationPackageNames()
+
+ verifier.collectHosts(packageNames, verifyStatusFilter)
+ .map { (domainSetId, packageName, hosts) ->
+ hosts.map { host ->
+ async {
+ if (isActive && !isStopped) {
+ val (_, status, statement) = verifier.verifyHost(
+ host,
+ packageName,
+ params.network
+ )
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ VerifyResult(host, status, groups)
+ } else {
+ // If the job gets cancelled, stop the remaining hosts, but continue the
+ // job to commit the results for hosts that were already requested.
+ null
+ }
+ }
+ }.awaitAll().filterNotNull().groupBy { it.status }
+ .forEach { (status, results) ->
+ val error = verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ results.map { it.host }.toSet(),
+ status.value
+ )
+ if (error == DomainVerificationManager.STATUS_OK
+ && status == VerifyStatus.SUCCESS
+ ) {
+ updateUriRelativeFilterGroups(
+ packageName,
+ results.associateBy({ it.host }, { it.groups })
+ )
+ }
+ }
+ }
+
+ // Succeed regardless of results since this retry is best effort and not required
+ Result.success()
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
index f83601a7807b..e8b4df9b0943 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -17,18 +17,9 @@
package com.android.statementservice.domain.worker
import android.content.Context
-import android.content.UriRelativeFilterGroup
-import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.NetworkType
import androidx.work.WorkerParameters
import com.android.statementservice.domain.VerifyStatus
-import com.android.statementservice.utils.AndroidUtils
-import com.android.statementservice.utils.StatementUtils
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.isActive
-import java.util.UUID
/**
* Scheduled every 24 hours with [NetworkType.CONNECTED] and every 72 hours without any constraints
@@ -37,63 +28,7 @@ import java.util.UUID
class RetryRequestWorker(
appContext: Context,
params: WorkerParameters
-) : BaseRequestWorker(appContext, params) {
+) : PeriodicUpdateWorker(appContext, params) {
- data class VerifyResult(
- val domainSetId: UUID,
- val host: String,
- val status: VerifyStatus,
- val packageName: String,
- val groups: List<UriRelativeFilterGroup>
- )
-
- override suspend fun doWork() = coroutineScope {
- if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
- return@coroutineScope Result.success()
- }
-
- val packageNames = verificationManager.queryValidVerificationPackageNames()
-
- verifier.collectHosts(packageNames)
- .map { (domainSetId, packageName, host) ->
- async {
- if (isActive && !isStopped) {
- val (_, status, statement) = verifier.verifyHost(host, packageName, params.network)
- val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
- StatementUtils.createUriRelativeFilterGroup(it)
- }
- VerifyResult(domainSetId, host, status, packageName, groups)
- } else {
- // If the job gets cancelled, stop the remaining hosts, but continue the
- // job to commit the results for hosts that were already requested.
- null
- }
- }
- }
- .awaitAll()
- .filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
- .groupBy { it.packageName }
- .forEach { (packageName, resultsByName) ->
- val groupUpdates = mutableMapOf<String, List<UriRelativeFilterGroup>>()
- resultsByName.groupBy { it.domainSetId }
- .forEach { (domainSetId, resultsById) ->
- resultsById.groupBy { it.status }
- .forEach { (status, verifyResults) ->
- val error = verificationManager.setDomainVerificationStatus(
- domainSetId,
- verifyResults.map(VerifyResult::host).toSet(),
- status.value
- )
- if (error == DomainVerificationManager.STATUS_OK
- && status == VerifyStatus.SUCCESS) {
- verifyResults.forEach { groupUpdates[it.host] = it.groups }
- }
- }
- }
- updateUriRelativeFilterGroups(packageName, groupUpdates)
- }
-
- // Succeed regardless of results since this retry is best effort and not required
- Result.success()
- }
+ override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::shouldRetry)
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
new file mode 100644
index 000000000000..c6f40c832860
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+
+class UpdateVerifiedDomainsWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : PeriodicUpdateWorker(appContext, params) {
+
+ override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::canUpdate)
+} \ No newline at end of file
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e0117368515b..11cb0703d353 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -385,6 +385,9 @@
is ready -->
<uses-permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" />
+ <!-- To be able to decipher default applications for certain roles in shortcut helper -->
+ <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -459,7 +462,7 @@
android:label="@string/screenshot_scroll_label"
android:finishOnTaskLaunch="true" />
- <service android:name=".screenshot.ScreenshotProxyService"
+ <service android:name=".screenshot.proxy.ScreenshotProxyService"
android:permission="com.android.systemui.permission.SELF"
android:exported="false" />
@@ -490,6 +493,7 @@
<activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
android:exported="true"
android:showForAllUsers="true"
+ android:excludeFromRecents="true"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
<action android:name="com.android.systemui.action.TOUCHPAD_TUTORIAL"/>
@@ -500,6 +504,7 @@
<activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
android:exported="true"
android:showForAllUsers="true"
+ android:excludeFromRecents="true"
android:theme="@style/Theme.AppCompat.NoActionBar">
<intent-filter>
<action android:name="com.android.systemui.action.TOUCHPAD_KEYBOARD_TUTORIAL"/>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index e47704ebdf86..cc01071c91a4 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -55,14 +55,6 @@
"exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget"
}
]
- },
- {
- "name": "AndroidAutomotiveNotificationsTests",
- "options" : [
- {
- "include-filter": "android.platform.tests.NotificationTest"
- }
- ]
}
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index a7b91c2b2f6a..0f210e7e5e7b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -41,7 +41,7 @@
android:exported="true"
android:label="@string/accessibility_menu_settings_name"
android:launchMode="singleTop"
- android:theme="@style/SettingsTheme">
+ android:theme="@style/Theme.SettingsBase">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a138fa9be613..41691552f714 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -21,11 +21,6 @@
<item name="android:colorControlNormal">@color/colorControlNormal</item>
</style>
- <style name="SettingsTheme" parent="Theme.SettingsBase">
- <!-- Quick fix so that the preference page doesn't render under its parent header. -->
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
- </style>
-
<!--The basic theme for service and test case only-->
<style name="A11yMenuBaseTheme" parent="android:Theme.DeviceDefault.Light">
<item name="android:windowActionBar">false</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 129dd9b3c14d..3f7ce2c33007 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -21,15 +21,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.graphics.Insets;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.widget.TextView;
import android.window.OnBackInvokedCallback;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
@@ -94,6 +97,18 @@ public class A11yMenuSettingsActivity extends FragmentActivity {
super.onViewCreated(view, savedInstanceState);
view.setLayoutDirection(
view.getResources().getConfiguration().getLayoutDirection());
+ view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @NonNull
+ @Override
+ public WindowInsets onApplyWindowInsets(@NonNull View v,
+ @NonNull WindowInsets windowInsets) {
+ Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.displayCutout());
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsets.CONSUMED;
+ }
+ });
}
/**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 6bc0f42f39aa..a60778658c59 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -57,7 +57,6 @@ import androidx.annotation.UiContext;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -383,9 +382,7 @@ public class A11yMenuOverlayLayout {
return;
}
snackbar.setText(text);
- if (Flags.a11yMenuSnackbarLiveRegion()) {
- snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
- }
+ snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
// Remove any existing fade-out animation before starting any new animations.
mHandler.removeCallbacksAndMessages(null);
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 10d7352da7dc..e3f5378175d2 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -12,3 +12,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "cont_auth_plugin"
+ namespace: "biometrics_framework"
+ description: "Plugin and related API hooks for contextual auth plugins"
+ bug: "373600589"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 20b83e10e821..ee229158decc 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -156,13 +156,6 @@ flag {
}
flag {
- name: "notifications_improved_hun_animation"
- namespace: "systemui"
- description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations."
- bug: "243302608"
-}
-
-flag {
name: "notification_content_alpha_optimization"
namespace: "systemui"
description: "Only reset alpha values of needed content views"
@@ -434,6 +427,18 @@ flag {
}
}
+
+flag {
+ name: "status_bar_chips_modernization"
+ namespace: "systemui"
+ description: "Deprecate OngoingCallController and implement OngoingActivityChips"
+ "in compose"
+ bug: "372657935"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
flag {
name: "status_bar_use_repos_for_call_chip"
namespace: "systemui"
@@ -1201,17 +1206,27 @@ flag {
}
flag {
- name: "communal_standalone_support"
+ name: "communal_hub_use_thread_pool_for_widgets"
namespace: "systemui"
- description: "Support communal features without a dock"
- bug: "352301247"
+ description: "Use a dedicated thread pool executor for loading widgets on glanceable hub"
+ bug: "369412569"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
- name: "communal_hub_on_mobile"
+ name: "communal_responsive_grid"
namespace: "systemui"
- description: "Brings the glanceable hub experience to mobile phones"
- bug: "375689917"
+ description: "Enables responsive grid on glanceable hub"
+ bug: "378171351"
+}
+
+flag {
+ name: "communal_standalone_support"
+ namespace: "systemui"
+ description: "Support communal features without a dock"
+ bug: "352301247"
}
flag {
@@ -1307,6 +1322,13 @@ flag {
}
flag {
+ name: "media_controls_ui_update"
+ namespace: "systemui"
+ description: "Enables media visuals update"
+ bug: "380053768"
+}
+
+flag {
namespace: "systemui"
name: "enable_view_capture_tracing"
description: "Enables view capture tracing in System UI."
@@ -1772,16 +1794,6 @@ flag {
}
flag {
- name: "ensure_enr_views_visibility"
- namespace: "systemui"
- description: "Ensures public and private visibilities"
- bug: "361552380"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "shade_expands_on_status_bar_long_press"
namespace: "systemui"
description: "Expands the shade on long press of any status bar"
@@ -1853,3 +1865,17 @@ flag {
description: "Implement the depth push scaling effect on Launcher when users pull down shade."
bug: "370562309"
}
+
+flag {
+ name: "spatial_model_app_pushback"
+ namespace: "systemui"
+ description: "Implement the depth push scaling effect on the current app when users pull down shade."
+ bug: "370560660"
+}
+
+flag {
+ name: "expanded_privacy_indicators_on_large_screen"
+ namespace: "systemui"
+ description: "Larger privacy indicators on large screen"
+ bug: "381864715"
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index 0f5e3679cc5f..ca2b9578f2be 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -16,6 +16,8 @@
package com.android.systemui.animation;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
@@ -39,6 +41,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -90,7 +93,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
() -> {
mStartTransaction = t;
mFinishCallback = finishCallback;
- startAnimationInternal(info);
+ startAnimationInternal(info, /* states= */ null);
});
}
@@ -112,7 +115,13 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback,
WindowAnimationState[] states) {
- logD("takeOverAnimation - " + info);
+ logD("takeOverAnimation - info=" + info + ", states=" + Arrays.toString(states));
+ mHandler.post(
+ () -> {
+ mStartTransaction = t;
+ mFinishCallback = finishCallback;
+ startAnimationInternal(info, states);
+ });
}
@Override
@@ -121,14 +130,19 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
mHandler.post(this::cancel);
}
- private void startAnimationInternal(TransitionInfo info) {
+ private void startAnimationInternal(
+ TransitionInfo info, @Nullable WindowAnimationState[] states) {
if (!prepareUIs(info)) {
logE("Unable to prepare UI!");
finishAnimation(/* finished= */ false);
return;
}
// Notify player that we are starting.
- mPlayer.onStart(info, mStartTransaction, mOrigin, mOriginTransaction);
+ mPlayer.onStart(info, states, mStartTransaction, mOrigin, mOriginTransaction);
+
+ // Apply the initial transactions in case the player forgot to apply them.
+ mOriginTransaction.commit();
+ mStartTransaction.apply();
// Start the animator.
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
@@ -205,7 +219,8 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
.setCornerRadius(leash, windowRadius)
.setWindowCrop(leash, bounds.width(), bounds.height());
}
- } else if (TransitionUtil.isClosingMode(mode)) {
+ } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+ // TRANSIT_CHANGE refers to the closing window in predictive back animation.
closingSurfaces.add(change.getLeash());
// For closing surfaces, starting bounds are base bounds. Apply corner radius if
// it's full screen.
@@ -236,13 +251,8 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
// Attach origin UIComponent to origin leash.
mOriginTransaction = mOrigin.newTransaction();
- mOriginTransaction
- .attachToTransitionLeash(
- mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height())
- .commit();
-
- // Apply all surface changes.
- mStartTransaction.apply();
+ mOriginTransaction.attachToTransitionLeash(
+ mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height());
return true;
}
@@ -328,6 +338,58 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
/* baseBounds= */ maxBounds);
}
+ private static void applyWindowAnimationStates(
+ TransitionInfo info,
+ @Nullable WindowAnimationState[] states,
+ UIComponent closingApp,
+ UIComponent openingApp) {
+ if (states == null) {
+ // Nothing to apply.
+ return;
+ }
+ // Calculate bounds.
+ Rect maxClosingBounds = new Rect();
+ Rect maxOpeningBounds = new Rect();
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ Rect bound = getBounds(states[i]);
+ if (bound == null) {
+ continue;
+ }
+ int mode = info.getChanges().get(i).getMode();
+ if (TransitionUtil.isOpeningMode(mode)) {
+ maxOpeningBounds.union(bound);
+ } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+ // TRANSIT_CHANGE refers to the closing window in predictive back animation.
+ maxClosingBounds.union(bound);
+ }
+ }
+
+ // Intentionally use a new transaction instead of reusing the existing transaction since we
+ // want to apply window animation states first without committing any other pending changes
+ // in the existing transaction. The existing transaction is expected to be committed by the
+ // onStart() client callback together with client's custom transformation.
+ UIComponent.Transaction transaction = closingApp.newTransaction();
+ if (!maxClosingBounds.isEmpty()) {
+ logD("Applying closing window bounds: " + maxClosingBounds);
+ transaction.setBounds(closingApp, maxClosingBounds);
+ }
+ if (!maxOpeningBounds.isEmpty()) {
+ logD("Applying opening window bounds: " + maxOpeningBounds);
+ transaction.setBounds(openingApp, maxOpeningBounds);
+ }
+ transaction.commit();
+ }
+
+ @Nullable
+ private static Rect getBounds(@Nullable WindowAnimationState state) {
+ if (state == null || state.bounds == null) {
+ return null;
+ }
+ Rect out = new Rect();
+ state.bounds.roundOut(out);
+ return out;
+ }
+
/**
* An interface that represents an origin transitions.
*
@@ -338,9 +400,14 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
/**
* Called when an origin transition starts. This method exposes the raw {@link
* TransitionInfo} so that clients can extract more information from it.
+ *
+ * <p>Note: if this transition is taking over a predictive back animation, the {@link
+ * WindowAnimationState} will be passed to this method. The concrete implementation is
+ * expected to apply the {@link WindowAnimationState} before continuing the transition.
*/
default void onStart(
TransitionInfo transitionInfo,
+ @Nullable WindowAnimationState[] states,
SurfaceControl.Transaction sfTransaction,
UIComponent origin,
UIComponent.Transaction uiTransaction) {
@@ -351,12 +418,15 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
.registerTransactionForClass(
SurfaceUIComponent.class,
new SurfaceUIComponent.Transaction(sfTransaction));
- // Wrap surfaces and start.
- onStart(
- transactions,
- origin,
- wrapSurfaces(transitionInfo, /* isOpening= */ false),
- wrapSurfaces(transitionInfo, /* isOpening= */ true));
+ // Wrap surfaces.
+ UIComponent closingApp = wrapSurfaces(transitionInfo, /* isOpening= */ false);
+ UIComponent openingApp = wrapSurfaces(transitionInfo, /* isOpening= */ true);
+
+ // Restore the pending animation states coming from predictive back transition.
+ applyWindowAnimationStates(transitionInfo, states, closingApp, openingApp);
+
+ // Start.
+ onStart(transactions, origin, closingApp, openingApp);
}
/**
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 9cef43c3deba..0317d5f095a1 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -57,6 +57,14 @@ public class ViewUIComponent implements UIComponent {
mView = view;
}
+ /**
+ * @return the view wrapped by this UI component.
+ * @hide
+ */
+ public View getView() {
+ return mView;
+ }
+
@Override
public float getAlpha() {
return mView.getAlpha();
@@ -89,7 +97,6 @@ public class ViewUIComponent implements UIComponent {
mSurfaceControl =
new SurfaceControl.Builder().setName("ViewUIComponent").setBufferSize(w, h).build();
mSurface = new Surface(mSurfaceControl);
- forceDraw();
// Attach surface to transition leash
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -99,7 +106,13 @@ public class ViewUIComponent implements UIComponent {
mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
// Make the view invisible AFTER the surface is shown.
- t.addTransactionCommittedListener(mView::post, () -> mView.setVisibility(View.INVISIBLE))
+ t.addTransactionCommittedListener(
+ mView::post,
+ () -> {
+ logD("Surface attached!");
+ forceDraw();
+ mView.setVisibility(View.INVISIBLE);
+ })
.apply();
}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
index 3cbb688ce7ca..3b66460140c9 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
@@ -16,8 +16,10 @@
package com.android.systemui.animation.server;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -26,6 +28,7 @@ import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -51,8 +54,8 @@ import java.util.function.Predicate;
/** An implementation of the {@link IOriginTransitions}. */
public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
- private static final boolean DEBUG = true;
private static final String TAG = "OriginTransitions";
+ private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
private final ShellTransitions mShellTransitions;
@@ -149,18 +152,7 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
if (DEBUG) {
Log.d(TAG, "startAnimation: " + info);
}
- if (!mOnStarting.test(info)) {
- Log.w(TAG, "Skipping cancelled transition " + mTransition);
- t.addTransactionCommittedListener(
- mExecutor,
- () -> {
- try {
- finishCallback.onTransitionFinished(null, null);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to report finish.", e);
- }
- })
- .apply();
+ if (maybeInterceptTransition(info, t, finishCallback)) {
return;
}
mTransition.startAnimation(token, info, t, finishCallback);
@@ -191,6 +183,9 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
if (DEBUG) {
Log.d(TAG, "takeOverAnimation: " + info);
}
+ if (maybeInterceptTransition(info, t, finishCallback)) {
+ return;
+ }
mTransition.takeOverAnimation(transition, info, t, finishCallback, states);
}
@@ -207,6 +202,27 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
public String toString() {
return "RemoteTransitionDelegate{transition=" + mTransition + "}";
}
+
+ private boolean maybeInterceptTransition(
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ if (!mOnStarting.test(info)) {
+ Log.w(TAG, "Intercepting cancelled transition " + mTransition);
+ t.addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ try {
+ finishCallback.onTransitionFinished(null, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to report finish.", e);
+ }
+ })
+ .apply();
+ return true;
+ }
+ return false;
+ }
}
/** A data record containing the origin transition pieces. */
@@ -229,13 +245,25 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
if (mDestroyed) {
return false;
}
- TransitionFilter filter = createFilterForReverseTransition(info);
+ TransitionFilter filter =
+ createFilterForReverseTransition(
+ info, /* forPredictiveBackTakeover= */ false);
if (filter != null) {
if (DEBUG) {
Log.d(TAG, "Registering filter " + filter);
}
mShellTransitions.registerRemote(filter, mWrappedReturnTransition);
}
+ TransitionFilter takeoverFilter =
+ createFilterForReverseTransition(
+ info, /* forPredictiveBackTakeover= */ true);
+ if (takeoverFilter != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Registering filter for takeover " + takeoverFilter);
+ }
+ mShellTransitions.registerRemoteForTakeover(
+ takeoverFilter, mWrappedReturnTransition);
+ }
return true;
}
}
@@ -331,7 +359,8 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
}
@Nullable
- private static TransitionFilter createFilterForReverseTransition(TransitionInfo info) {
+ private static TransitionFilter createFilterForReverseTransition(
+ TransitionInfo info, boolean forPredictiveBackTakeover) {
TaskInfo launchingTaskInfo = null;
TaskInfo launchedTaskInfo = null;
ComponentName launchingActivity = null;
@@ -365,7 +394,9 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
if (DEBUG) {
Log.d(
TAG,
- "createFilterForReverseTransition: launchingTaskInfo="
+ "createFilterForReverseTransition: forPredictiveBackTakeover="
+ + forPredictiveBackTakeover
+ + ", launchingTaskInfo="
+ launchingTaskInfo
+ ", launchedTaskInfo="
+ launchedTaskInfo
@@ -395,8 +426,21 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
+ " cookie!");
return null;
}
+ if (forPredictiveBackTakeover && launchedTaskInfo == null) {
+ // Predictive back take over currently only support cross-task transition.
+ Log.d(
+ TAG,
+ "createFilterForReverseTransition: skipped - unable to find launched task"
+ + " for predictive back takeover");
+ return null;
+ }
TransitionFilter filter = new TransitionFilter();
- filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ if (forPredictiveBackTakeover) {
+ filter.mTypeSet = new int[] {TRANSIT_PREPARE_BACK_NAVIGATION};
+ } else {
+ filter.mTypeSet =
+ new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK, TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ }
// The opening activity of the return transition must match the activity we just closed.
TransitionFilter.Requirement req1 = new TransitionFilter.Requirement();
@@ -405,15 +449,18 @@ public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity;
TransitionFilter.Requirement req2 = new TransitionFilter.Requirement();
- req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ if (forPredictiveBackTakeover) {
+ req2.mModes = new int[] {TRANSIT_CHANGE};
+ } else {
+ req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ }
if (launchedTaskInfo != null) {
// For task transitions, the closing task's cookie must match the task we just
// launched.
req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0);
} else {
// For activity transitions, the closing activity of the return transition must
- // match
- // the activity we just launched.
+ // match the activity we just launched.
req2.mTopActivity = launchedActivity;
}
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 f1cbba7272b0..41a00f5237f7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -424,15 +424,16 @@ constructor(
newKeyguardOccludedState: Boolean?
) {
super.onTransitionAnimationCancelled(newKeyguardOccludedState)
- cleanUp()
+ onDispose()
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationEnd(isExpandingFullyAbove)
- cleanUp()
+ onDispose()
}
- private fun cleanUp() {
+ override fun onDispose() {
+ super.onDispose()
cleanUpRunnable?.run()
}
}
@@ -560,6 +561,7 @@ constructor(
cookie: TransitionCookie? = null,
component: ComponentName? = null,
returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues.
@@ -587,6 +589,7 @@ constructor(
cookie,
component,
returnCujType,
+ isEphemeral,
)
}
}
@@ -647,6 +650,9 @@ constructor(
* appropriately.
*/
fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+
+ /** The controller will not be used again. Clean up the relevant internal state. */
+ fun onDispose() {}
}
/**
@@ -702,6 +708,8 @@ constructor(
object : Controller by controller {
override val isLaunching: Boolean = false
}
+ // Cross-task close transitions should not use this animation, so we only register it for
+ // when the opening window is Launcher.
val returnFilter =
TransitionFilter().apply {
mRequirements =
@@ -710,7 +718,11 @@ constructor(
mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
mTopActivity = component
- }
+ },
+ TransitionFilter.Requirement().apply {
+ mActivityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+ mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
+ },
)
}
val returnRemoteTransition =
@@ -861,6 +873,9 @@ constructor(
) {
// Raise closing task to "above" layer so it isn't covered.
t.setLayer(target.leash, aboveLayers - i)
+ } else if (TransitionUtil.isOpeningType(change.mode)) {
+ // Put into the "below" layer space.
+ t.setLayer(target.leash, belowLayers - i)
}
} else if (TransitionInfo.isIndependent(change, info)) {
// Root tasks
@@ -1141,7 +1156,7 @@ constructor(
// If a [controller.windowAnimatorState] exists, treat this like a takeover.
takeOverAnimationInternal(
window,
- startWindowStates = null,
+ startWindowState = null,
startTransaction = null,
callback,
)
@@ -1156,22 +1171,23 @@ constructor(
callback: IRemoteAnimationFinishedCallback?,
) {
val window = setUpAnimation(apps, callback) ?: return
- takeOverAnimationInternal(window, startWindowStates, startTransaction, callback)
+ val startWindowState = startWindowStates[apps!!.indexOf(window)]
+ takeOverAnimationInternal(window, startWindowState, startTransaction, callback)
}
private fun takeOverAnimationInternal(
window: RemoteAnimationTarget,
- startWindowStates: Array<WindowAnimationState>?,
+ startWindowState: WindowAnimationState?,
startTransaction: SurfaceControl.Transaction?,
callback: IRemoteAnimationFinishedCallback?,
) {
val useSpring =
- !controller.isLaunching && startWindowStates != null && startTransaction != null
+ !controller.isLaunching && startWindowState != null && startTransaction != null
startAnimation(
window,
navigationBar = null,
useSpring,
- startWindowStates,
+ startWindowState,
startTransaction,
callback,
)
@@ -1281,7 +1297,7 @@ constructor(
window: RemoteAnimationTarget,
navigationBar: RemoteAnimationTarget? = null,
useSpring: Boolean = false,
- startingWindowStates: Array<WindowAnimationState>? = null,
+ startingWindowState: WindowAnimationState? = null,
startTransaction: SurfaceControl.Transaction? = null,
iCallback: IRemoteAnimationFinishedCallback? = null,
) {
@@ -1327,6 +1343,7 @@ constructor(
val isExpandingFullyAbove =
transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
+ val windowState = startingWindowState ?: controller.windowAnimatorState
// We animate the opening window and delegate the view expansion to [this.controller].
val delegate = this.controller
@@ -1349,18 +1366,6 @@ constructor(
}
}
- // The states are sorted matching the changes inside the transition info.
- // Using this info, the RemoteAnimationTargets are created, with their
- // prefixOrderIndex fields in reverse order to that of changes. To extract
- // the right state, we need to invert again.
- val windowState =
- if (startingWindowStates != null) {
- startingWindowStates[
- startingWindowStates.size - window.prefixOrderIndex]
- } else {
- controller.windowAnimatorState
- }
-
// TODO(b/323863002): use the timestamp and velocity to update the initial
// position.
val bounds = windowState?.bounds
@@ -1449,12 +1454,6 @@ constructor(
delegate.onTransitionAnimationProgress(state, progress, linearProgress)
}
}
- val windowState =
- if (startingWindowStates != null) {
- startingWindowStates[startingWindowStates.size - window.prefixOrderIndex]
- } else {
- controller.windowAnimatorState
- }
val velocityPxPerS =
if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
@@ -1473,6 +1472,7 @@ constructor(
fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
drawHole = !controller.isBelowAnimatingWindow,
startVelocity = velocityPxPerS,
+ startFrameTime = windowState?.timestamp ?: -1,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 3ba9a2974846..b56a68cb2dd6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -39,7 +39,8 @@ interface Expandable {
launchCujType: Int? = null,
cookie: ActivityTransitionAnimator.TransitionCookie? = null,
component: ComponentName? = null,
- returnCujType: Int? = null
+ returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): ActivityTransitionAnimator.Controller?
/**
@@ -55,7 +56,8 @@ interface Expandable {
launchCujType,
cookie = null,
component = null,
- returnCujType = null
+ returnCujType = null,
+ isEphemeral = true,
)
}
@@ -80,14 +82,16 @@ interface Expandable {
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
return ActivityTransitionAnimator.Controller.fromView(
view,
launchCujType,
cookie,
component,
- returnCujType
+ returnCujType,
+ isEphemeral,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index e626c04675e1..558c1eba2c1c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -67,6 +67,12 @@ constructor(
/** The [CujType] associated to this return animation. */
private val returnCujType: Int? = null,
+
+ /**
+ * Whether this controller should be invalidated after its first use, and whenever [ghostedView]
+ * is detached.
+ */
+ private val isEphemeral: Boolean = false,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
@@ -119,6 +125,19 @@ constructor(
returnCujType
}
+ /**
+ * Used to automatically clean up the internal state once [ghostedView] is detached from the
+ * hierarchy.
+ */
+ private val detachListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ onDispose()
+ }
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
if (ghostedView !is LaunchableView) {
@@ -155,6 +174,16 @@ constructor(
}
background = findBackground(ghostedView)
+
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ ghostedView.addOnAttachStateChangeListener(detachListener)
+ }
+ }
+
+ override fun onDispose() {
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ ghostedView.removeOnAttachStateChangeListener(detachListener)
+ }
}
/**
@@ -164,7 +193,7 @@ constructor(
protected open fun setBackgroundCornerRadius(
background: Drawable,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
// By default, we rely on WrappedDrawable to set/restore the background radii before/after
// each draw.
@@ -195,7 +224,7 @@ constructor(
val state =
TransitionAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
- bottomCornerRadius = getCurrentBottomCornerRadius()
+ bottomCornerRadius = getCurrentBottomCornerRadius(),
)
fillGhostedViewState(state)
return state
@@ -269,7 +298,7 @@ constructor(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
val ghostView = this.ghostView ?: return
val backgroundView = this.backgroundView!!
@@ -317,11 +346,11 @@ constructor(
scale,
scale,
ghostedViewState.centerX - transitionContainerLocation[0],
- ghostedViewState.centerY - transitionContainerLocation[1]
+ ghostedViewState.centerY - transitionContainerLocation[1],
)
ghostViewMatrix.postTranslate(
(leftChange + rightChange) / 2f,
- (topChange + bottomChange) / 2f
+ (topChange + bottomChange) / 2f,
)
ghostView.animationMatrix = ghostViewMatrix
@@ -462,7 +491,7 @@ constructor(
private fun updateRadii(
radii: FloatArray,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
radii[0] = topCornerRadius
radii[1] = topCornerRadius
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index cbe11a3f2f60..8a57e8cbbb20 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -163,7 +164,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
t.show(wallpapers[i].leash);
t.setAlpha(wallpapers[i].leash, 1.f);
}
- if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
+ if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) {
resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t);
}
} else {
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 e2bc4095e1b5..4e889e946a5f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -27,6 +27,8 @@ import android.graphics.drawable.GradientDrawable
import android.util.FloatProperty
import android.util.Log
import android.util.MathUtils
+import android.util.TimeUtils
+import android.view.Choreographer
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
@@ -366,6 +368,7 @@ class TransitionAnimator(
@get:VisibleForTesting val springY: SpringAnimation,
@get:VisibleForTesting val springScale: SpringAnimation,
private val springState: SpringState,
+ private val startFrameTime: Long,
private val onAnimationStart: Runnable,
) : Animation {
@get:VisibleForTesting
@@ -374,6 +377,42 @@ class TransitionAnimator(
override fun start() {
onAnimationStart.run()
+
+ // If no start frame time is provided, we start the springs normally.
+ if (startFrameTime < 0) {
+ startSprings()
+ return
+ }
+
+ // This function is not guaranteed to be called inside a frame. We try to access the
+ // frame time immediately, but if we're not inside a frame this will throw an exception.
+ // We must then post a callback to be run at the beginning of the next frame.
+ try {
+ initAndStartSprings(Choreographer.getInstance().frameTime)
+ } catch (_: IllegalStateException) {
+ Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
+ initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS)
+ }
+ }
+ }
+
+ private fun initAndStartSprings(frameTime: Long) {
+ // Initialize the spring as if it had started at the time that its start state
+ // was created.
+ springX.doAnimationFrame(startFrameTime)
+ springY.doAnimationFrame(startFrameTime)
+ springScale.doAnimationFrame(startFrameTime)
+ // Move the spring time forward to the current frame, so it updates its internal state
+ // following the initial momentum over the elapsed time.
+ springX.doAnimationFrame(frameTime)
+ springY.doAnimationFrame(frameTime)
+ springScale.doAnimationFrame(frameTime)
+ // Actually start the spring. We do this after the previous calls because the framework
+ // doesn't like it when you call doAnimationFrame() after start() with an earlier time.
+ startSprings()
+ }
+
+ private fun startSprings() {
springX.start()
springY.start()
springScale.start()
@@ -471,7 +510,9 @@ class TransitionAnimator(
* is true.
*
* 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.
+ * 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
+ * should be started.
*/
fun startAnimation(
controller: Controller,
@@ -480,6 +521,7 @@ class TransitionAnimator(
fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
startVelocity: PointF? = null,
+ startFrameTime: Long = -1,
): Animation {
if (!controller.isLaunching) assertReturnAnimations()
if (startVelocity != null) assertLongLivedReturnAnimations()
@@ -502,6 +544,7 @@ class TransitionAnimator(
fadeWindowBackgroundLayer,
drawHole,
startVelocity,
+ startFrameTime,
)
.apply { start() }
}
@@ -515,6 +558,7 @@ class TransitionAnimator(
fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
startVelocity: PointF? = null,
+ startFrameTime: Long = -1,
): Animation {
val transitionContainer = controller.transitionContainer
val transitionContainerOverlay = transitionContainer.overlay
@@ -537,6 +581,7 @@ class TransitionAnimator(
startState,
endState,
startVelocity,
+ startFrameTime,
windowBackgroundLayer,
transitionContainer,
transitionContainerOverlay,
@@ -722,6 +767,7 @@ class TransitionAnimator(
startState: State,
endState: State,
startVelocity: PointF,
+ startFrameTime: Long,
windowBackgroundLayer: GradientDrawable,
transitionContainer: View,
transitionContainerOverlay: ViewGroupOverlay,
@@ -912,7 +958,7 @@ class TransitionAnimator(
}
}
- return MultiSpringAnimation(springX, springY, springScale, springState) {
+ return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) {
onAnimationStart(
controller,
isExpandingFullyAbove,
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
new file mode 100644
index 000000000000..92b6fd44e2f2
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiParameter
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.getContainingUFile
+
+class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes() = listOf(UClass::class.java)
+
+ override fun createUastHandler(context: JavaContext) =
+ object : UElementHandler() {
+ override fun visitClass(node: UClass) {
+ for (constructor in node.constructors) {
+ // Visit all injected constructors in shade-relevant packages
+ if (!constructor.hasAnnotation(INJECT_ANNOTATION)) continue
+ if (!isInRelevantShadePackage(node)) continue
+ if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue
+
+ for (parameter in constructor.parameterList.parameters) {
+ if (parameter.shouldReport()) {
+ context.report(
+ issue = ISSUE,
+ scope = parameter.declarationScope,
+ location = context.getNameLocation(parameter),
+ message = reportMsg(className = parameter.type.presentableText),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val INJECT_ANNOTATION = "javax.inject.Inject"
+ private const val APPLICATION_ANNOTATION =
+ "com.android.systemui.dagger.qualifiers.Application"
+ private const val GLOBAL_CONFIG_ANNOTATION = "com.android.systemui.common.ui.GlobalConfig"
+ private const val SHADE_DISPLAY_AWARE_ANNOTATION =
+ "com.android.systemui.shade.ShadeDisplayAware"
+
+ private const val CONTEXT = "android.content.Context"
+ private const val WINDOW_MANAGER = "android.view.WindowManager"
+ private const val LAYOUT_INFLATER = "android.view.LayoutInflater"
+ private const val RESOURCES = "android.content.res.Resources"
+ private const val CONFIG_STATE = "com.android.systemui.common.ui.ConfigurationState"
+ private const val CONFIG_CONTROLLER =
+ "com.android.systemui.statusbar.policy.ConfigurationController"
+ private const val CONFIG_INTERACTOR =
+ "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor"
+
+ private val CONTEXT_DEPENDENT_SHADE_CLASSES =
+ setOf(
+ CONTEXT,
+ WINDOW_MANAGER,
+ LAYOUT_INFLATER,
+ RESOURCES,
+ CONFIG_STATE,
+ CONFIG_CONTROLLER,
+ CONFIG_INTERACTOR,
+ )
+
+ private val CONFIG_CLASSES = setOf(CONFIG_STATE, CONFIG_CONTROLLER, CONFIG_INTERACTOR)
+
+ private val SHADE_WINDOW_PACKAGES =
+ listOf(
+ "com.android.systemui.biometrics",
+ "com.android.systemui.bouncer",
+ "com.android.systemui.keyboard.docking.ui.viewmodel",
+ "com.android.systemui.qs",
+ "com.android.systemui.shade",
+ "com.android.systemui.statusbar.notification",
+ "com.android.systemui.unfold.domain.interactor",
+ )
+
+ private val IGNORED_PACKAGES =
+ setOf(
+ "com.android.systemui.biometrics.UdfpsController",
+ "com.android.systemui.qs.customize.TileAdapter",
+ )
+
+ private fun PsiParameter.shouldReport(): Boolean {
+ val className = type.canonicalText
+
+ // check if the parameter is a context-dependent class relevant to shade
+ if (className !in CONTEXT_DEPENDENT_SHADE_CLASSES) return false
+ // check if it has @ShadeDisplayAware
+ if (hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)) return false
+ // check if its a @Application-annotated Context
+ if (className == CONTEXT && hasAnnotation(APPLICATION_ANNOTATION)) return false
+ // check if its a @GlobalConfig-annotated ConfigurationState, ConfigurationController
+ // or ConfigurationInteractor
+ if (className in CONFIG_CLASSES && hasAnnotation(GLOBAL_CONFIG_ANNOTATION)) return false
+
+ return true
+ }
+
+ private fun isInRelevantShadePackage(node: UClass): Boolean {
+ val packageName = node.getContainingUFile()?.packageName
+ if (packageName.isNullOrBlank()) return false
+ return SHADE_WINDOW_PACKAGES.any { relevantPackage ->
+ packageName.startsWith(relevantPackage)
+ }
+ }
+
+ private fun reportMsg(className: String) =
+ "UI elements of the shade window should use " +
+ "ShadeDisplayAware-annotated $className, as the shade might move between windows, " +
+ "and only @ShadeDisplayAware resources are updated with the new configuration " +
+ "correctly. Failures to do so might result in wrong dimensions for shade window " +
+ "classes (e.g. using the wrong density or theme). If the usage of $className is " +
+ "not related to display specific configuration or UI, then there is technically " +
+ "no need to use the annotation, and you can annotate the class with " +
+ "@SuppressLint(\"ShadeDisplayAwareContextChecker\")/" +
+ "@Suppress(\"ShadeDisplayAwareContextChecker\")".trimMargin()
+
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "ShadeDisplayAwareContextChecker",
+ briefDescription = "Using non-ShadeDisplayAware component within shade",
+ explanation =
+ """
+ Any context-dependent components (Resources, LayoutInflater, ConfigurationState,
+ etc.) being injected into Shade-relevant classes must have the @ShadeDisplayAware
+ annotation to ensure they work with when the shade is moved to a different display.
+ When the shade is moved, the configuration might change, and only @ShadeDisplayAware
+ components will update accordingly to reflect the new display.
+ """
+ .trimIndent(),
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.ERROR,
+ implementation =
+ Implementation(ShadeDisplayAwareDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index a1f4f5507e5f..6d18f9377806 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -46,8 +46,9 @@ class SystemUIIssueRegistry : IssueRegistry() {
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
MissingApacheLicenseDetector.ISSUE,
+ ShadeDisplayAwareDetector.ISSUE,
RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,
- RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR
+ RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR,
)
override val api: Int
@@ -60,6 +61,6 @@ class SystemUIIssueRegistry : IssueRegistry() {
Vendor(
vendorName = "Android",
feedbackUrl = "http://b/issues/new?component=78010",
- contact = "jernej@google.com"
+ contact = "jernej@google.com",
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
new file mode 100644
index 000000000000..79f190782ee8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.internal.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.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = ShadeDisplayAwareDetector()
+
+ override fun getIssues(): List<Issue> = listOf(ShadeDisplayAwareDetector.ISSUE)
+
+ private val qsContext: TestFile =
+ java(
+ """
+ package com.android.systemui.qs.dagger;
+
+ import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+ import java.lang.annotation.Retention;
+
+ @Retention(RUNTIME) public @interface QSThemedContext {}
+ """
+ )
+ .indented()
+
+ private val injectStub: TestFile =
+ kotlin(
+ """
+ package javax.inject
+
+ @Retention(AnnotationRetention.RUNTIME) annotation class Inject
+ """
+ )
+ .indented()
+
+ private val shadeDisplayAwareStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.shade
+
+ @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware
+ """
+ )
+ .indented()
+
+ private val applicationStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.dagger.qualifiers
+
+ @Retention(AnnotationRetention.RUNTIME) annotation class Application
+ """
+ )
+ .indented()
+
+ private val globalConfigStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.common.ui
+
+ @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
+ """
+ )
+ .indented()
+
+ private val configStateStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.common.ui
+
+ class ConfigurationState
+ """
+ )
+ .indented()
+
+ private val configControllerStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.statusbar.policy
+
+ class ConfigurationController
+ """
+ )
+ .indented()
+
+ private val configInteractorStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.common.ui.domain.interactor
+
+ class ConfigurationInteractor
+ """
+ )
+ .indented()
+
+ private val otherStubs =
+ arrayOf(
+ injectStub,
+ qsContext,
+ shadeDisplayAwareStub,
+ applicationStub,
+ globalConfigStub,
+ configStateStub,
+ configControllerStub,
+ configInteractorStub,
+ )
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.Context
+
+ class ExampleClass
+ @Inject
+ constructor(private val context: Context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectErrorCount(1)
+ .expectContains(errorMsgString(8, "Context"))
+ .expectContains("[ShadeDisplayAwareContextChecker]")
+ .expectContains(
+ "constructor(private val context: Context)\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains("1 errors, 0 warnings")
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withMultipleRelevantParameters_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.Context
+ import android.content.res.Resources
+ import android.view.LayoutInflater
+ import android.view.WindowManager
+ import com.android.systemui.common.ui.ConfigurationState
+ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+ import com.android.systemui.statusbar.policy.ConfigurationController
+
+ class ExampleClass
+ @Inject
+ constructor(
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val configState: ConfigurationState,
+ private val configController: ConfigurationController,
+ private val configInteractor: ConfigurationInteractor,
+ )
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectErrorCount(6)
+ .expectContains(errorMsgString(lineNumber = 15, className = "Context"))
+ .expectContains(
+ "private val context: Context,\n" + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(errorMsgString(lineNumber = 16, className = "LayoutInflater"))
+ .expectContains(
+ "private val inflater: LayoutInflater,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(errorMsgString(lineNumber = 17, className = "WindowManager"))
+ .expectContains(
+ "private val windowManager: WindowManager,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(errorMsgString(lineNumber = 18, className = "ConfigurationState"))
+ .expectContains(
+ "private val configState: ConfigurationState,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(errorMsgString(lineNumber = 19, className = "ConfigurationController"))
+ .expectContains(
+ "private val configController: ConfigurationController,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(errorMsgString(lineNumber = 20, className = "ConfigurationInteractor"))
+ .expectContains(
+ "private val configInteractor: ConfigurationInteractor,\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+ )
+ .expectContains(" [ShadeDisplayAwareContextChecker]")
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withRelevantParameter_withAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.shade.ShadeDisplayAware
+
+ class ExampleClass
+ @Inject
+ constructor(@ShadeDisplayAware private val context: Context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withoutRelevantParameter_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.ContextWrapper
+
+ class ExampleClass
+ @Inject
+ constructor(private val contextWrapper: ContextWrapper)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withApplicationAnnotatedContext() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.Context
+ import com.android.systemui.dagger.qualifiers.Application
+
+ class ExampleClass
+ @Inject
+ constructor(@Application private val context: Context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withGlobalConfigAnnotatedConfigurationClass() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import com.android.systemui.common.ui.ConfigurationState
+ import com.android.systemui.common.ui.GlobalConfig
+
+ class ExampleClass
+ @Inject
+ constructor(@GlobalConfig private val configState: ConfigurationState)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_notInRelevantPackage_withRelevantParameter_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.keyboard
+
+ import javax.inject.Inject
+ import android.content.Context
+
+ class ExampleClass @Inject constructor(private val context: Context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun nonInjectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import android.content.Context
+
+ class ExampleClass(private val context: Context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation_suppressed() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package com.android.systemui.shade.example
+
+ import javax.inject.Inject
+ import android.content.Context
+
+ @Suppress("ShadeDisplayAwareContextChecker")
+ class ExampleClass
+ @Inject
+ constructor(
+ private val context: Context
+ )
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun injectedConstructor_inExemptPackage_withRelevantParameter_withoutAnnotation() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package com.android.systemui.qs.customize;
+
+ import javax.inject.Inject;
+ import com.android.systemui.qs.dagger.QSThemedContext;
+ import android.content.Context;
+
+ public class TileAdapter {
+ @Inject
+ public TileAdapter(@QSThemedContext Context context) {}
+ }
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ *otherStubs,
+ )
+ .issues(ShadeDisplayAwareDetector.ISSUE)
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
+
+ private fun errorMsgString(lineNumber: Int, className: String) =
+ "src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of " +
+ "the shade window should use ShadeDisplayAware-annotated $className, as the shade " +
+ "might move between windows, and only @ShadeDisplayAware resources are updated with " +
+ "the new configuration correctly. Failures to do so might result in wrong dimensions " +
+ "for shade window classes (e.g. using the wrong density or theme). If the usage of " +
+ "$className is not related to display specific configuration or UI, then there is " +
+ "technically no need to use the annotation, and you can annotate the class with " +
+ "@SuppressLint(\"ShadeDisplayAwareContextChecker\")" +
+ "/@Suppress(\"ShadeDisplayAwareContextChecker\")"
+}
diff --git a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
index efbdf4d1533d..0abeeb7d62a8 100644
--- a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
@@ -20,7 +20,7 @@ import com.android.app.tracing.coroutines.createCoroutineTracingContext
import kotlin.coroutines.CoroutineContext
fun newTracingContext(name: String): CoroutineContext {
- return createCoroutineTracingContext(name, walkStackForDefaultNames = true) { className ->
+ return createCoroutineTracingContext(name, walkStackForDefaultNames = false) { className ->
className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") ||
className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached")
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index a55df2b36a80..103a9b5cf5f4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -52,6 +52,9 @@ import kotlin.math.roundToInt
interface ExpandableController {
/** The [Expandable] controlled by this controller. */
val expandable: Expandable
+
+ /** Called when the [Expandable] stop being included in the composition. */
+ fun onDispose()
}
/**
@@ -88,33 +91,44 @@ fun rememberExpandableController(
// Whether this composable is still composed. We only do the dialog exit animation if this is
// true.
val isComposed = remember { mutableStateOf(true) }
- DisposableEffect(Unit) { onDispose { isComposed.value = false } }
-
- return remember(
- color,
- contentColor,
- shape,
- borderStroke,
- composeViewRoot,
- density,
- layoutDirection,
- ) {
- ExpandableControllerImpl(
+
+ val controller =
+ remember(
color,
contentColor,
shape,
borderStroke,
composeViewRoot,
density,
- animatorState,
- isDialogShowing,
- overlay,
- currentComposeViewInOverlay,
- boundsInComposeViewRoot,
layoutDirection,
- isComposed,
- )
+ ) {
+ ExpandableControllerImpl(
+ color,
+ contentColor,
+ shape,
+ borderStroke,
+ composeViewRoot,
+ density,
+ animatorState,
+ isDialogShowing,
+ overlay,
+ currentComposeViewInOverlay,
+ boundsInComposeViewRoot,
+ layoutDirection,
+ isComposed,
+ )
+ }
+
+ DisposableEffect(Unit) {
+ onDispose {
+ isComposed.value = false
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ controller.onDispose()
+ }
+ }
}
+
+ return controller
}
internal class ExpandableControllerImpl(
@@ -132,19 +146,29 @@ internal class ExpandableControllerImpl(
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
+ /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
+ private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null
+
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(launchCujType, cookie, component, returnCujType)
+ val controller = activityController(launchCujType, cookie, component, returnCujType)
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = controller
+ }
+
+ return controller
}
override fun dialogTransitionController(
@@ -158,6 +182,11 @@ internal class ExpandableControllerImpl(
}
}
+ override fun onDispose() {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = null
+ }
+
/**
* Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
* dialog animation. This controller will:
@@ -181,7 +210,7 @@ internal class ExpandableControllerImpl(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
// We copy state given that it's always the same object that is mutated by
// ActivityTransitionAnimator.
@@ -269,7 +298,7 @@ internal class ExpandableControllerImpl(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
return object :
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
new file mode 100644
index 000000000000..9fe85b7a7070
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -0,0 +1,497 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.horizontalDrag
+import androidx.compose.foundation.gestures.verticalDrag
+import androidx.compose.foundation.overscroll
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerId
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.input.pointer.util.addPointerInputChange
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.util.fastAny
+import com.android.compose.modifiers.thenIf
+import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.launch
+
+/**
+ * A draggable that plays nicely with the nested scroll mechanism.
+ *
+ * This can be used whenever you need a draggable inside a scrollable or a draggable that contains a
+ * scrollable.
+ */
+interface NestedDraggable {
+ /**
+ * Called when a drag is started in the given [position] (*before* dragging the touch slop) and
+ * in the direction given by [sign].
+ */
+ fun onDragStarted(position: Offset, sign: Float): Controller
+
+ /**
+ * Whether this draggable should consume any scroll amount with the given [sign] coming from a
+ * nested scrollable.
+ *
+ * This is called whenever a nested scrollable does not consume some scroll amount. If this
+ * returns `true`, then [onDragStarted] will be called and this draggable will have priority and
+ * consume all future events during preScroll until the nested scroll is finished.
+ */
+ fun shouldConsumeNestedScroll(sign: Float): Boolean
+
+ interface Controller {
+ /**
+ * Drag by [delta] pixels.
+ *
+ * @return the consumed [delta]. Any non-consumed delta will be dispatched to the next
+ * nested scroll connection to be consumed by any composable above in the hierarchy. If
+ * the drag was performed on this draggable directly (instead of on a nested scrollable),
+ * any remaining delta will be used to overscroll this draggable.
+ */
+ fun onDrag(delta: Float): Float
+
+ /**
+ * Stop the current drag with the given [velocity].
+ *
+ * @return the consumed [velocity]. Any non-consumed velocity will be dispatched to the next
+ * nested scroll connection to be consumed by any composable above in the hierarchy. If
+ * the drag was performed on this draggable directly (instead of on a nested scrollable),
+ * any remaining velocity will be used to animate the overscroll of this draggable.
+ */
+ suspend fun onDragStopped(velocity: Float): Float
+ }
+}
+
+/**
+ * A draggable that supports nested scrolling and overscroll effects.
+ *
+ * @see NestedDraggable
+ */
+fun Modifier.nestedDraggable(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect? = null,
+ enabled: Boolean = true,
+): Modifier {
+ return this.thenIf(overscrollEffect != null) { Modifier.overscroll(overscrollEffect) }
+ .then(NestedDraggableElement(draggable, orientation, overscrollEffect, enabled))
+}
+
+private data class NestedDraggableElement(
+ private val draggable: NestedDraggable,
+ private val orientation: Orientation,
+ private val overscrollEffect: OverscrollEffect?,
+ private val enabled: Boolean,
+) : ModifierNodeElement<NestedDraggableNode>() {
+ override fun create(): NestedDraggableNode {
+ return NestedDraggableNode(draggable, orientation, overscrollEffect, enabled)
+ }
+
+ override fun update(node: NestedDraggableNode) {
+ node.update(draggable, orientation, overscrollEffect, enabled)
+ }
+}
+
+private class NestedDraggableNode(
+ private var draggable: NestedDraggable,
+ override var orientation: Orientation,
+ private var overscrollEffect: OverscrollEffect?,
+ private var enabled: Boolean,
+) :
+ DelegatingNode(),
+ PointerInputModifierNode,
+ NestedScrollConnection,
+ CompositionLocalConsumerModifierNode,
+ OrientationAware {
+ private val nestedScrollDispatcher = NestedScrollDispatcher()
+ private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null
+ set(value) {
+ field?.let { undelegate(it) }
+ field = value?.also { delegate(it) }
+ }
+
+ private var detectDragsDelegate: SuspendingPointerInputModifierNode? = null
+ set(value) {
+ field?.let { undelegate(it) }
+ field = value?.also { delegate(it) }
+ }
+
+ /** The controller created by the nested scroll logic (and *not* the drag logic). */
+ private var nestedScrollController: WrappedController? = null
+ set(value) {
+ field?.ensureOnDragStoppedIsCalled()
+ field = value
+ }
+
+ /**
+ * The last pointer which was the first down since the last time all pointers were up.
+ *
+ * This is use to track the started position of a drag started on a nested scrollable.
+ */
+ private var lastFirstDown: Offset? = null
+
+ init {
+ delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
+ }
+
+ override fun onDetach() {
+ nestedScrollController?.ensureOnDragStoppedIsCalled()
+ }
+
+ fun update(
+ draggable: NestedDraggable,
+ orientation: Orientation,
+ overscrollEffect: OverscrollEffect?,
+ enabled: Boolean,
+ ) {
+ this.draggable = draggable
+ this.orientation = orientation
+ this.overscrollEffect = overscrollEffect
+ this.enabled = enabled
+
+ trackDownPositionDelegate?.resetPointerInputHandler()
+ detectDragsDelegate?.resetPointerInputHandler()
+ nestedScrollController?.ensureOnDragStoppedIsCalled()
+
+ if (!enabled && trackDownPositionDelegate != null) {
+ check(detectDragsDelegate != null)
+ trackDownPositionDelegate = null
+ detectDragsDelegate = null
+ }
+ }
+
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize,
+ ) {
+ if (!enabled) return
+
+ if (trackDownPositionDelegate == null) {
+ check(detectDragsDelegate == null)
+ trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() }
+ detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() }
+ }
+
+ checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds)
+ checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds)
+ }
+
+ override fun onCancelPointerInput() {
+ trackDownPositionDelegate?.onCancelPointerInput()
+ detectDragsDelegate?.onCancelPointerInput()
+ }
+
+ /*
+ * ======================================
+ * ===== Pointer input (drag) logic =====
+ * ======================================
+ */
+
+ private suspend fun PointerInputScope.detectDrags() {
+ // Lazily create the velocity tracker when the pointer input restarts.
+ val velocityTracker = VelocityTracker()
+
+ awaitEachGesture {
+ val down = awaitFirstDown(requireUnconsumed = false)
+ var overSlop = 0f
+ val onTouchSlopReached = { change: PointerInputChange, over: Float ->
+ change.consume()
+ overSlop = over
+ }
+
+ suspend fun AwaitPointerEventScope.awaitTouchSlopOrCancellation(
+ pointerId: PointerId
+ ): PointerInputChange? {
+ return when (orientation) {
+ Orientation.Horizontal ->
+ awaitHorizontalTouchSlopOrCancellation(pointerId, onTouchSlopReached)
+ Orientation.Vertical ->
+ awaitVerticalTouchSlopOrCancellation(pointerId, onTouchSlopReached)
+ }
+ }
+
+ var drag = awaitTouchSlopOrCancellation(down.id)
+
+ // We try to pick-up the drag gesture in case the touch slop swipe was consumed by a
+ // nested scrollable child that disappeared.
+ // This was copied from http://shortn/_10L8U02IoL.
+ // TODO(b/380838584): Reuse detect(Horizontal|Vertical)DragGestures() instead.
+ while (drag == null && currentEvent.changes.fastAny { it.pressed }) {
+ var event: PointerEvent
+ do {
+ event = awaitPointerEvent()
+ } while (
+ event.changes.fastAny { it.isConsumed } && event.changes.fastAny { it.pressed }
+ )
+
+ // An event was not consumed and there's still a pointer in the screen.
+ if (event.changes.fastAny { it.pressed }) {
+ // Await touch slop again, using the initial down as starting point.
+ // For most cases this should return immediately since we probably moved
+ // far enough from the initial down event.
+ drag = awaitTouchSlopOrCancellation(down.id)
+ }
+ }
+
+ if (drag != null) {
+ velocityTracker.resetTracking()
+
+ val sign = (drag.position - down.position).toFloat().sign
+ val wrappedController =
+ WrappedController(coroutineScope, draggable.onDragStarted(down.position, sign))
+ if (overSlop != 0f) {
+ onDrag(wrappedController, drag, overSlop, velocityTracker)
+ }
+
+ // If a drag was started, we cancel any other drag started by a nested scrollable.
+ //
+ // Note: we cancel the nested drag here *after* starting the new drag so that in the
+ // STL case, the cancelled drag will not change the current scene of the STL.
+ nestedScrollController?.ensureOnDragStoppedIsCalled()
+
+ val isSuccessful =
+ try {
+ val onDrag = { change: PointerInputChange ->
+ onDrag(
+ wrappedController,
+ change,
+ change.positionChange().toFloat(),
+ velocityTracker,
+ )
+ change.consume()
+ }
+
+ when (orientation) {
+ Orientation.Horizontal -> horizontalDrag(drag.id, onDrag)
+ Orientation.Vertical -> verticalDrag(drag.id, onDrag)
+ }
+ } catch (t: Throwable) {
+ wrappedController.ensureOnDragStoppedIsCalled()
+ throw t
+ }
+
+ if (isSuccessful) {
+ val maxVelocity = currentValueOf(LocalViewConfiguration).maximumFlingVelocity
+ val velocity =
+ velocityTracker
+ .calculateVelocity(Velocity(maxVelocity, maxVelocity))
+ .toFloat()
+ onDragStopped(wrappedController, velocity)
+ } else {
+ onDragStopped(wrappedController, velocity = 0f)
+ }
+ }
+ }
+ }
+
+ private fun onDrag(
+ controller: NestedDraggable.Controller,
+ change: PointerInputChange,
+ delta: Float,
+ velocityTracker: VelocityTracker,
+ ) {
+ velocityTracker.addPointerInputChange(change)
+
+ scrollWithOverscroll(delta) { deltaFromOverscroll ->
+ scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll ->
+ controller.onDrag(deltaFromNestedScroll)
+ }
+ }
+ }
+
+ private fun onDragStopped(controller: WrappedController, velocity: Float) {
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ try {
+ flingWithOverscroll(velocity) { velocityFromOverscroll ->
+ flingWithNestedScroll(velocityFromOverscroll) { velocityFromNestedScroll ->
+ controller.onDragStopped(velocityFromNestedScroll)
+ }
+ }
+ } finally {
+ controller.ensureOnDragStoppedIsCalled()
+ }
+ }
+ }
+
+ private fun scrollWithOverscroll(delta: Float, performScroll: (Float) -> Float): Float {
+ val effect = overscrollEffect
+ return if (effect != null) {
+ effect
+ .applyToScroll(delta.toOffset(), source = NestedScrollSource.UserInput) {
+ performScroll(it.toFloat()).toOffset()
+ }
+ .toFloat()
+ } else {
+ performScroll(delta)
+ }
+ }
+
+ private fun scrollWithNestedScroll(delta: Float, performScroll: (Float) -> Float): Float {
+ val preConsumed =
+ nestedScrollDispatcher
+ .dispatchPreScroll(
+ available = delta.toOffset(),
+ source = NestedScrollSource.UserInput,
+ )
+ .toFloat()
+ val available = delta - preConsumed
+ val consumed = performScroll(available)
+ val left = available - consumed
+ val postConsumed =
+ nestedScrollDispatcher
+ .dispatchPostScroll(
+ consumed = (preConsumed + consumed).toOffset(),
+ available = left.toOffset(),
+ source = NestedScrollSource.UserInput,
+ )
+ .toFloat()
+ return consumed + preConsumed + postConsumed
+ }
+
+ private suspend fun flingWithOverscroll(
+ velocity: Float,
+ performFling: suspend (Float) -> Float,
+ ) {
+ val effect = overscrollEffect
+ if (effect != null) {
+ effect.applyToFling(velocity.toVelocity()) { performFling(it.toFloat()).toVelocity() }
+ } else {
+ performFling(velocity)
+ }
+ }
+
+ private suspend fun flingWithNestedScroll(
+ velocity: Float,
+ performFling: suspend (Float) -> Float,
+ ): Float {
+ val preConsumed = nestedScrollDispatcher.dispatchPreFling(available = velocity.toVelocity())
+ val available = velocity - preConsumed.toFloat()
+ val consumed = performFling(available)
+ val left = available - consumed
+ return nestedScrollDispatcher
+ .dispatchPostFling(
+ consumed = consumed.toVelocity() + preConsumed,
+ available = left.toVelocity(),
+ )
+ .toFloat()
+ }
+
+ /*
+ * ===============================
+ * ===== Nested scroll logic =====
+ * ===============================
+ */
+
+ private suspend fun PointerInputScope.trackDownPosition() {
+ awaitEachGesture { lastFirstDown = awaitFirstDown(requireUnconsumed = false).position }
+ }
+
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val controller = nestedScrollController ?: return Offset.Zero
+ val consumed = controller.onDrag(available.toFloat())
+ return consumed.toOffset()
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource,
+ ): Offset {
+ if (source == NestedScrollSource.SideEffect) {
+ check(nestedScrollController == null)
+ return Offset.Zero
+ }
+
+ val offset = available.toFloat()
+ if (offset == 0f) {
+ return Offset.Zero
+ }
+
+ val sign = offset.sign
+ if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
+ val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
+ nestedScrollController =
+ WrappedController(coroutineScope, draggable.onDragStarted(startedPosition, sign))
+ }
+
+ val controller = nestedScrollController ?: return Offset.Zero
+ return controller.onDrag(offset).toOffset()
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ val controller = nestedScrollController ?: return Velocity.Zero
+ nestedScrollController = null
+
+ val consumed = controller.onDragStopped(available.toFloat())
+ return consumed.toVelocity()
+ }
+}
+
+/**
+ * A controller that wraps [delegate] and can be used to ensure that [onDragStopped] is called, but
+ * not more than once.
+ */
+private class WrappedController(
+ private val coroutineScope: CoroutineScope,
+ private val delegate: NestedDraggable.Controller,
+) : NestedDraggable.Controller by delegate {
+ private var onDragStoppedCalled = false
+
+ override fun onDrag(delta: Float): Float {
+ if (onDragStoppedCalled) return 0f
+ return delegate.onDrag(delta)
+ }
+
+ override suspend fun onDragStopped(velocity: Float): Float {
+ if (onDragStoppedCalled) return 0f
+ onDragStoppedCalled = true
+ return delegate.onDragStopped(velocity)
+ }
+
+ fun ensureOnDragStoppedIsCalled() {
+ // Start with UNDISPATCHED so that onDragStopped() is always run until its first suspension
+ // point, even if coroutineScope is cancelled.
+ coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { onDragStopped(velocity = 0f) }
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt
new file mode 100644
index 000000000000..6e91727e68e3
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * An interface to conveniently convert a [Float] to and from an [Offset] or a [Velocity] given an
+ * [orientation].
+ */
+interface OrientationAware {
+ val orientation: Orientation
+
+ fun Float.toOffset(): Offset {
+ return when (orientation) {
+ Orientation.Horizontal -> Offset(x = this, y = 0f)
+ Orientation.Vertical -> Offset(x = 0f, y = this)
+ }
+ }
+
+ fun Float.toVelocity(): Velocity {
+ return when (orientation) {
+ Orientation.Horizontal -> Velocity(x = this, y = 0f)
+ Orientation.Vertical -> Velocity(x = 0f, y = this)
+ }
+ }
+
+ fun Offset.toFloat(): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> this.x
+ Orientation.Vertical -> this.y
+ }
+ }
+
+ fun Velocity.toFloat(): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> this.x
+ Orientation.Vertical -> this.y
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
new file mode 100644
index 000000000000..fd3902fa7dc8
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -0,0 +1,443 @@
+/*
+ * 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.compose.gesture
+
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.unit.Velocity
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.awaitCancellation
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class NestedDraggableTest(override val orientation: Orientation) : OrientationAware {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun orientations() = listOf(Orientation.Horizontal, Orientation.Vertical)
+ }
+
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun simpleDrag() {
+ val draggable = TestDraggable()
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+ assertThat(draggable.onDragCalled).isFalse()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ var rootCenter = Offset.Zero
+ rule.onRoot().performTouchInput {
+ rootCenter = center
+ down(center)
+ moveBy((touchSlop + 10f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragCalled).isTrue()
+ assertThat(draggable.onDragDelta).isEqualTo(10f)
+ assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter)
+ assertThat(draggable.onDragStartedSign).isEqualTo(1f)
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ rule.onRoot().performTouchInput { moveBy(20f.toOffset()) }
+
+ assertThat(draggable.onDragDelta).isEqualTo(30f)
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ moveBy((-15f).toOffset())
+ up()
+ }
+
+ assertThat(draggable.onDragDelta).isEqualTo(15f)
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun nestedScrollable() {
+ val draggable = TestDraggable()
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ .nestedScrollable(rememberScrollState())
+ )
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+ assertThat(draggable.onDragCalled).isFalse()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ var rootCenter = Offset.Zero
+ rule.onRoot().performTouchInput {
+ rootCenter = center
+ down(center)
+ moveBy((-touchSlop - 10f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragCalled).isTrue()
+ assertThat(draggable.onDragDelta).isEqualTo(-10f)
+ assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter)
+ assertThat(draggable.onDragStartedSign).isEqualTo(-1f)
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ rule.onRoot().performTouchInput { moveBy((-20f).toOffset()) }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragCalled).isTrue()
+ assertThat(draggable.onDragDelta).isEqualTo(-30f)
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ moveBy(15f.toOffset())
+ up()
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragCalled).isTrue()
+ assertThat(draggable.onDragDelta).isEqualTo(-15f)
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset() {
+ val draggable = TestDraggable()
+ var orientation by mutableStateOf(orientation)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(touchSlop.toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ orientation =
+ when (orientation) {
+ Orientation.Horizontal -> Orientation.Vertical
+ Orientation.Vertical -> Orientation.Horizontal
+ }
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset_nestedScroll() {
+ val draggable = TestDraggable()
+ var orientation by mutableStateOf(orientation)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ .nestedScrollable(rememberScrollState())
+ )
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 1f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ orientation =
+ when (orientation) {
+ Orientation.Horizontal -> Orientation.Vertical
+ Orientation.Vertical -> Orientation.Horizontal
+ }
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag() {
+ val draggable = TestDraggable()
+ var composeContent by mutableStateOf(true)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ if (composeContent) {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(touchSlop.toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ composeContent = false
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag_nestedScroll() {
+ val draggable = TestDraggable()
+ var composeContent by mutableStateOf(true)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ if (composeContent) {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ .nestedScrollable(rememberScrollState())
+ )
+ }
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 1f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ composeContent = false
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringFling() {
+ val draggable = TestDraggable()
+ var composeContent by mutableStateOf(true)
+ var preFlingCalled = false
+ rule.setContent {
+ if (composeContent) {
+ Box(
+ Modifier.fillMaxSize()
+ // This nested scroll connection indefinitely suspends on pre fling, so that
+ // we can emulate what happens when the draggable is removed from
+ // composition while the pre-fling happens and onDragStopped() was not
+ // called yet.
+ .nestedScroll(
+ remember {
+ object : NestedScrollConnection {
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ preFlingCalled = true
+ awaitCancellation()
+ }
+ }
+ }
+ )
+ .nestedDraggable(draggable, orientation)
+ )
+ }
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ // Swipe down.
+ rule.onRoot().performTouchInput {
+ when (orientation) {
+ Orientation.Horizontal -> swipeLeft()
+ Orientation.Vertical -> swipeDown()
+ }
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+ assertThat(preFlingCalled).isTrue()
+
+ composeContent = false
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ @Ignore("b/303224944#comment22")
+ fun onDragStoppedIsCalledWhenNestedScrollableIsRemoved() {
+ val draggable = TestDraggable()
+ var composeNestedScrollable by mutableStateOf(true)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ .then(
+ if (composeNestedScrollable) {
+ Modifier.nestedScrollable(rememberScrollState())
+ } else {
+ Modifier
+ }
+ )
+ )
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 1f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ composeNestedScrollable = false
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ @Test
+ fun enabled() {
+ val draggable = TestDraggable()
+ var enabled by mutableStateOf(false)
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation, enabled = enabled)
+ )
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(touchSlop.toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ enabled = true
+ rule.onRoot().performTouchInput {
+ // Release previously up finger.
+ up()
+
+ down(center)
+ moveBy(touchSlop.toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ assertThat(draggable.onDragStoppedCalled).isFalse()
+
+ enabled = false
+ rule.waitForIdle()
+ assertThat(draggable.onDragStoppedCalled).isTrue()
+ }
+
+ private fun ComposeContentTestRule.setContentWithTouchSlop(
+ content: @Composable () -> Unit
+ ): Float {
+ var touchSlop = 0f
+ setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ content()
+ }
+ return touchSlop
+ }
+
+ private fun Modifier.nestedScrollable(scrollState: ScrollState): Modifier {
+ return when (orientation) {
+ Orientation.Vertical -> verticalScroll(scrollState)
+ Orientation.Horizontal -> horizontalScroll(scrollState)
+ }
+ }
+
+ private class TestDraggable(
+ private val onDragStarted: (Offset, Float) -> Unit = { _, _ -> },
+ private val onDrag: (Float) -> Float = { it },
+ private val onDragStopped: suspend (Float) -> Float = { it },
+ private val shouldConsumeNestedScroll: (Float) -> Boolean = { true },
+ ) : NestedDraggable {
+ var onDragStartedCalled = false
+ var onDragCalled = false
+ var onDragStoppedCalled = false
+
+ var onDragStartedPosition = Offset.Zero
+ var onDragStartedSign = 0f
+ var onDragDelta = 0f
+
+ override fun onDragStarted(position: Offset, sign: Float): NestedDraggable.Controller {
+ onDragStartedCalled = true
+ onDragStartedPosition = position
+ onDragStartedSign = sign
+ onDragDelta = 0f
+
+ onDragStarted.invoke(position, sign)
+ return object : NestedDraggable.Controller {
+ override fun onDrag(delta: Float): Float {
+ onDragCalled = true
+ onDragDelta += delta
+ return onDrag.invoke(delta)
+ }
+
+ override suspend fun onDragStopped(velocity: Float): Float {
+ onDragStoppedCalled = true
+ return onDragStopped.invoke(velocity)
+ }
+ }
+ }
+
+ override fun shouldConsumeNestedScroll(sign: Float): Boolean {
+ return shouldConsumeNestedScroll.invoke(sign)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index ae92d259d62b..85f549d43a11 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
@@ -71,9 +72,7 @@ constructor(
}
@Composable
- override fun SceneScope.Content(
- modifier: Modifier,
- ) =
+ override fun SceneScope.Content(modifier: Modifier) =
BouncerScene(
viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() },
dialogFactory = dialogFactory,
@@ -89,6 +88,8 @@ private fun SceneScope.BouncerScene(
) {
val backgroundColor = MaterialTheme.colorScheme.surface
+ DisposableEffect(Unit) { onDispose { viewModel.onUiDestroyed() } }
+
Box(modifier) {
Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
drawRect(color = backgroundColor)
@@ -101,7 +102,7 @@ private fun SceneScope.BouncerScene(
dialogFactory,
Modifier.element(Bouncer.Elements.Content)
.sysuiResTag(Bouncer.TestTags.Root)
- .fillMaxSize()
+ .fillMaxSize(),
)
}
}
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 4705d8dd86c4..beaf9631ae5a 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
@@ -44,7 +44,6 @@ import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -188,7 +187,7 @@ fun CommunalContainer(
scene(
CommunalScenes.Blank,
userActions =
- if (communalHubOnMobile()) emptyMap()
+ if (viewModel.v2FlagEnabled()) emptyMap()
else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
) {
// This scene shows nothing only allowing for transitions to the communal scene.
@@ -198,7 +197,8 @@ fun CommunalContainer(
scene(
CommunalScenes.Communal,
userActions =
- if (communalHubOnMobile()) emptyMap() else mapOf(Swipe.End to CommunalScenes.Blank),
+ if (viewModel.v2FlagEnabled()) emptyMap()
+ else mapOf(Swipe.End to CommunalScenes.Blank),
) {
CommunalScene(
backgroundType = backgroundType,
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 778d7e7f99b3..3926b326dacd 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
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
@@ -59,7 +60,7 @@ constructor(
Box(modifier = Modifier.fillMaxSize()) {
with(communalPopupSection) { Popup() }
with(ambientStatusBarSection) {
- AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
}
CommunalHub(
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 787edfb9168c..573e5ca5e2d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -66,6 +66,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
@@ -169,6 +170,7 @@ import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.Flags
+import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.communalTimerFlickerFix
import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -194,7 +196,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
import kotlin.math.min
-import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -693,7 +694,12 @@ private fun ResizableItemFrameWrapper(
onResize = onResize,
minHeightPx = minHeightPx,
maxHeightPx = maxHeightPx,
- resizeMultiple = CommunalContentSize.HALF.span,
+ resizeMultiple =
+ if (communalResponsiveGrid()) {
+ 1
+ } else {
+ CommunalContentSize.FixedSize.HALF.span
+ },
) {
content(Modifier)
}
@@ -701,14 +707,22 @@ private fun ResizableItemFrameWrapper(
}
@Composable
-fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): WidgetSizeInfo {
+fun calculateWidgetSize(
+ cellHeight: Dp?,
+ availableHeight: Dp?,
+ item: CommunalContentModel,
+ isResizable: Boolean,
+): WidgetSizeInfo {
val density = LocalDensity.current
+ val minHeight = cellHeight ?: CommunalContentSize.FixedSize.HALF.dp()
+ val maxHeight = availableHeight ?: CommunalContentSize.FixedSize.FULL.dp()
+
return if (isResizable && item is CommunalContentModel.WidgetContent.Widget) {
with(density) {
val minHeightPx =
(min(item.providerInfo.minResizeHeight, item.providerInfo.minHeight)
- .coerceAtLeast(CommunalContentSize.HALF.dp().toPx().roundToInt()))
+ .coerceAtLeast(minHeight.roundToPx()))
val maxHeightPx =
(if (item.providerInfo.maxResizeHeight > 0) {
@@ -716,7 +730,7 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge
} else {
Int.MAX_VALUE
})
- .coerceIn(minHeightPx, CommunalContentSize.FULL.dp().toPx().roundToInt())
+ .coerceIn(minHeightPx, maxHeight.roundToPx())
WidgetSizeInfo(minHeightPx, maxHeightPx)
}
@@ -725,6 +739,37 @@ fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): Widge
}
}
+@Composable
+private fun HorizontalGridWrapper(
+ contentPadding: PaddingValues,
+ gridState: LazyGridState,
+ modifier: Modifier = Modifier,
+ content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit,
+) {
+ if (communalResponsiveGrid()) {
+ ResponsiveLazyHorizontalGrid(
+ cellAspectRatio = 1.5f,
+ modifier = modifier,
+ state = gridState,
+ minContentPadding = contentPadding,
+ minHorizontalArrangement = Dimensions.ItemSpacing,
+ minVerticalArrangement = Dimensions.ItemSpacing,
+ content = content,
+ )
+ } else {
+ LazyHorizontalGrid(
+ modifier = modifier,
+ state = gridState,
+ rows = GridCells.Fixed(CommunalContentSize.FixedSize.FULL.span),
+ contentPadding = contentPadding,
+ horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+ verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+ ) {
+ content(null)
+ }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -778,28 +823,32 @@ private fun BoxScope.CommunalHubLazyGrid(
// Since the grid has its own listener for in-grid drag events, we use a separate element
// for android drag events.
Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
+ } else if (communalResponsiveGrid()) {
+ gridModifier = gridModifier.fillMaxSize()
} else {
gridModifier = gridModifier.height(hubDimensions.GridHeight)
}
- val itemArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing)
- LazyHorizontalGrid(
+ HorizontalGridWrapper(
modifier = gridModifier,
- state = gridState,
- rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+ gridState = gridState,
contentPadding = contentPadding,
- horizontalArrangement = itemArrangement,
- verticalArrangement = itemArrangement,
- ) {
+ ) { sizeInfo ->
itemsIndexed(
items = list,
key = { _, item -> item.key },
contentType = { _, item -> item.key },
- span = { _, item -> GridItemSpan(item.size.span) },
+ span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) },
) { index, item ->
- val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
+ val currentItemSpan = item.getSpanOrMax(sizeInfo?.gridSize?.height)
+ val dpSize =
+ if (sizeInfo != null) {
+ DpSize(sizeInfo.cellSize.width, sizeInfo.calculateHeight(currentItemSpan))
+ } else {
+ DpSize(Dimensions.CardWidth, (item.size as CommunalContentSize.FixedSize).dp())
+ }
+ val size = SizeF(dpSize.width.value, dpSize.height.value)
val selected = item.key == selectedKey.value
- val dpSize = DpSize(size.width.dp, size.height.dp)
val isResizable =
if (item is CommunalContentModel.WidgetContent.Widget) {
item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0
@@ -809,7 +858,7 @@ private fun BoxScope.CommunalHubLazyGrid(
val resizeableItemFrameViewModel =
rememberViewModel(
- key = item.size.span,
+ key = currentItemSpan,
traceName = "ResizeableItemFrame.viewModel.$index",
) {
ResizeableItemFrameViewModel()
@@ -822,13 +871,23 @@ private fun BoxScope.CommunalHubLazyGrid(
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "Widget resizing outline alpha",
)
- val widgetSizeInfo = calculateWidgetSize(item, isResizable)
+
+ val widgetSizeInfo =
+ calculateWidgetSize(
+ cellHeight = sizeInfo?.cellSize?.height,
+ availableHeight = sizeInfo?.availableHeight,
+ item = item,
+ isResizable = isResizable,
+ )
ResizableItemFrameWrapper(
key = item.key,
- currentSpan = GridItemSpan(item.size.span),
+ currentSpan = GridItemSpan(currentItemSpan),
gridState = gridState,
gridContentPadding = contentPadding,
- verticalArrangement = itemArrangement,
+ verticalArrangement =
+ Arrangement.spacedBy(
+ sizeInfo?.verticalArrangement ?: Dimensions.ItemSpacing
+ ),
enabled = selected,
alpha = { outlineAlpha },
modifier =
@@ -908,7 +967,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal
text = titleForEmptyStateCTA,
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
- color = colors.secondary,
+ color = colors.primary,
modifier =
Modifier.focusable().semantics(mergeDescendants = true) {
contentDescription = titleForEmptyStateCTA
@@ -1686,11 +1745,11 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn
}
}
-private fun CommunalContentSize.dp(): Dp {
+private fun CommunalContentSize.FixedSize.dp(): Dp {
return when (this) {
- CommunalContentSize.FULL -> Dimensions.CardHeightFull
- CommunalContentSize.HALF -> Dimensions.CardHeightHalf
- CommunalContentSize.THIRD -> Dimensions.CardHeightThird
+ CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull
+ CommunalContentSize.FixedSize.HALF -> Dimensions.CardHeightHalf
+ CommunalContentSize.FixedSize.THIRD -> Dimensions.CardHeightThird
}
}
@@ -1709,7 +1768,10 @@ class Dimensions(val context: Context, val config: Configuration) {
val GridTopSpacing: Dp
get() {
val result =
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (
+ communalResponsiveGrid() ||
+ config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ ) {
114.dp
} else {
val windowMetrics =
@@ -1729,7 +1791,7 @@ class Dimensions(val context: Context, val config: Configuration) {
get() = 530.adjustedDp
val ItemSpacing
- get() = 50.adjustedDp
+ get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp
val CardHeightHalf
get() = (CardHeightFull - ItemSpacing) / 2
@@ -1771,6 +1833,13 @@ class Dimensions(val context: Context, val config: Configuration) {
data class WidgetSizeInfo(val minHeightPx: Int, val maxHeightPx: Int)
+private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) =
+ if (maxSpan != null) {
+ size.span.coerceAtMost(maxSpan)
+ } else {
+ size.span
+ }
+
private object Colors {
val DisabledColorFilter by lazy { disabledColorMatrix() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
new file mode 100644
index 000000000000..3642127d0823
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -0,0 +1,238 @@
+/*
+ * 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
+
+import android.content.res.Configuration
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberOverscrollEffect
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.coerceAtMost
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+
+/**
+ * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
+ * the specified aspect ratio, but is otherwise resizeable in order to best fill the available
+ * space.
+ */
+@Composable
+fun ResponsiveLazyHorizontalGrid(
+ cellAspectRatio: Float,
+ modifier: Modifier = Modifier,
+ state: LazyGridState = rememberLazyGridState(),
+ minContentPadding: PaddingValues = PaddingValues(0.dp),
+ minHorizontalArrangement: Dp = 0.dp,
+ minVerticalArrangement: Dp = 0.dp,
+ flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+ userScrollEnabled: Boolean = true,
+ overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
+ content: LazyGridScope.(sizeInfo: SizeInfo) -> Unit,
+) {
+ check(cellAspectRatio > 0f) { "Aspect ratio must be greater than 0, but was $cellAspectRatio" }
+ check(minHorizontalArrangement.value >= 0f && minVerticalArrangement.value >= 0f) {
+ "Horizontal and vertical arrangements must be non-negative, but were " +
+ "$minHorizontalArrangement and $minVerticalArrangement, respectively."
+ }
+ BoxWithConstraints(modifier) {
+ val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight)
+ val layoutDirection = LocalLayoutDirection.current
+
+ val minStartPadding = minContentPadding.calculateStartPadding(layoutDirection)
+ val minEndPadding = minContentPadding.calculateEndPadding(layoutDirection)
+ val minTopPadding = minContentPadding.calculateTopPadding()
+ val minBottomPadding = minContentPadding.calculateBottomPadding()
+ val minHorizontalPadding = minStartPadding + minEndPadding
+ val minVerticalPadding = minTopPadding + minBottomPadding
+
+ // Determine the maximum allowed cell width and height based on the available width and
+ // height, and the desired number of columns and rows.
+ val maxCellWidth =
+ calculateCellSize(
+ availableSpace = maxWidth,
+ padding = minHorizontalPadding,
+ numCells = gridSize.width,
+ cellSpacing = minHorizontalArrangement,
+ )
+ val maxCellHeight =
+ calculateCellSize(
+ availableSpace = maxHeight,
+ padding = minVerticalPadding,
+ numCells = gridSize.height,
+ cellSpacing = minVerticalArrangement,
+ )
+
+ // Constrain the max size to the desired aspect ratio.
+ val finalSize =
+ calculateClosestSize(
+ maxWidth = maxCellWidth,
+ maxHeight = maxCellHeight,
+ aspectRatio = cellAspectRatio,
+ )
+
+ // Determine how much space in each dimension we've used up, and how much we have left as
+ // extra space. Distribute the extra space evenly along the content padding.
+ val usedWidth =
+ calculateUsedSpace(
+ cellSize = finalSize.width,
+ numCells = gridSize.width,
+ padding = minHorizontalPadding,
+ cellSpacing = minHorizontalArrangement,
+ )
+ .coerceAtMost(maxWidth)
+ val usedHeight =
+ calculateUsedSpace(
+ cellSize = finalSize.height,
+ numCells = gridSize.height,
+ padding = minVerticalPadding,
+ cellSpacing = minVerticalArrangement,
+ )
+ .coerceAtMost(maxHeight)
+ val extraWidth = maxWidth - usedWidth
+ val extraHeight = maxHeight - usedHeight
+
+ val finalContentPadding =
+ PaddingValues(
+ start = minStartPadding + extraWidth / 2,
+ end = minEndPadding + extraWidth / 2,
+ top = minTopPadding + extraHeight / 2,
+ bottom = minBottomPadding + extraHeight / 2,
+ )
+
+ LazyHorizontalGrid(
+ rows = GridCells.Fixed(gridSize.height),
+ modifier = Modifier.fillMaxSize(),
+ state = state,
+ contentPadding = finalContentPadding,
+ horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement),
+ verticalArrangement = Arrangement.spacedBy(minVerticalArrangement),
+ flingBehavior = flingBehavior,
+ userScrollEnabled = userScrollEnabled,
+ overscrollEffect = overscrollEffect,
+ ) {
+ content(
+ SizeInfo(
+ cellSize = finalSize,
+ contentPadding = finalContentPadding,
+ verticalArrangement = minVerticalArrangement,
+ maxHeight = maxHeight,
+ gridSize = gridSize,
+ )
+ )
+ }
+ }
+}
+
+private fun calculateCellSize(availableSpace: Dp, padding: Dp, numCells: Int, cellSpacing: Dp): Dp =
+ (availableSpace - padding - cellSpacing * (numCells - 1)) / numCells
+
+private fun calculateUsedSpace(cellSize: Dp, numCells: Int, padding: Dp, cellSpacing: Dp): Dp =
+ cellSize * numCells + padding + (numCells - 1) * cellSpacing
+
+private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float): DpSize {
+ return if (maxWidth / maxHeight > aspectRatio) {
+ // Target is too wide, shrink width
+ DpSize(maxHeight * aspectRatio, maxHeight)
+ } else {
+ // Target is too tall, shrink height
+ DpSize(maxWidth, maxWidth / aspectRatio)
+ }
+}
+
+/**
+ * Provides size info of the responsive grid, since the size is dynamic.
+ *
+ * @property cellSize The size of each cell in the grid.
+ * @property verticalArrangement The space between rows in the grid.
+ * @property gridSize The size of the grid, in cell units.
+ * @property availableHeight The maximum height an item in the grid may occupy.
+ */
+data class SizeInfo(
+ val cellSize: DpSize,
+ val verticalArrangement: Dp,
+ val gridSize: IntSize,
+ private val contentPadding: PaddingValues,
+ private val maxHeight: Dp,
+) {
+ val availableHeight: Dp
+ get() =
+ maxHeight -
+ contentPadding.calculateBottomPadding() -
+ contentPadding.calculateTopPadding()
+
+ /** Calculates the height in dp of a certain number of rows. */
+ fun calculateHeight(numRows: Int): Dp {
+ return numRows * cellSize.height + (numRows - 1) * verticalArrangement
+ }
+}
+
+@Composable
+private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize {
+ val configuration = LocalConfiguration.current
+ val orientation = configuration.orientation
+
+ return remember(orientation, maxWidth, maxHeight) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ IntSize(
+ width = calculateNumCellsWidth(maxWidth),
+ height = calculateNumCellsHeight(maxHeight),
+ )
+ } else {
+ // In landscape we invert the rows/columns to ensure we match the same area as portrait.
+ // This keeps the number of elements in the grid consistent when changing orientation.
+ IntSize(
+ width = calculateNumCellsHeight(maxWidth),
+ height = calculateNumCellsWidth(maxHeight),
+ )
+ }
+ }
+}
+
+private fun calculateNumCellsWidth(width: Dp) =
+ // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
+ when {
+ width >= 840.dp -> 3
+ width >= 600.dp -> 2
+ else -> 1
+ }
+
+private fun calculateNumCellsHeight(height: Dp) =
+ // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
+ when {
+ height >= 900.dp -> 3
+ height >= 480.dp -> 2
+ else -> 1
+ }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index 1475795e2dc6..d02215083679 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.composable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -29,7 +30,9 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -60,13 +63,25 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
@Composable
fun AlternateBouncer(
alternateBouncerDependencies: AlternateBouncerDependencies,
+ onHideAnimationFinished: () -> Unit,
modifier: Modifier = Modifier,
) {
val isVisible by
- alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle(
- initialValue = false
- )
+ alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle(true)
+ val visibleState = remember { MutableTransitionState(isVisible) }
+
+ // Feeds the isVisible value to the MutableTransitionState used by AnimatedVisibility below.
+ LaunchedEffect(isVisible) { visibleState.targetState = isVisible }
+
+ // Watches the MutableTransitionState and calls onHideAnimationFinished when the fade out
+ // animation is finished. This way the window view is removed from the view hierarchy only after
+ // the fade out animation is complete.
+ LaunchedEffect(visibleState.currentState, visibleState.isIdle) {
+ if (!visibleState.currentState && visibleState.isIdle) {
+ onHideAnimationFinished()
+ }
+ }
val udfpsIconLocation by
alternateBouncerDependencies.udfpsIconViewModel.iconLocation.collectAsStateWithLifecycle(
@@ -74,7 +89,7 @@ fun AlternateBouncer(
)
AnimatedVisibility(
- visible = isVisible,
+ visibleState = visibleState,
enter = fadeIn(),
exit = fadeOut(),
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 4f1acdc4da60..6591a75f2407 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -27,6 +27,7 @@ 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.layout.safeContentPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
@@ -95,7 +96,9 @@ private fun PeopleScreenWithConversations(
recentTiles: List<PeopleTileViewModel>,
onTileClicked: (PeopleTileViewModel) -> Unit,
) {
- Column(Modifier.sysuiResTag("top_level_with_conversations")) {
+ Column(
+ Modifier.fillMaxSize().safeContentPadding().sysuiResTag("top_level_with_conversations")
+ ) {
Column(
Modifier.fillMaxWidth().padding(PeopleSpacePadding),
horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
index d483f88edfff..9f582bc4daa9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
@@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@@ -47,7 +48,7 @@ import com.android.systemui.res.R
@Composable
internal fun PeopleScreenEmpty(onGotItClicked: () -> Unit) {
Column(
- Modifier.fillMaxSize().padding(PeopleSpacePadding),
+ Modifier.fillMaxSize().safeContentPadding().padding(PeopleSpacePadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
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 2d32fd768eaa..f7ce2153b0ec 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
@@ -261,7 +261,7 @@ private fun RowScope.ForegroundServicesButton(
/** A button with an icon. */
@Composable
-private fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) {
+fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) {
Expandable(
color = colorAttr(model.backgroundColor),
shape = CircleShape,
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 3fce89054b20..b1a19456ab7d 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,8 +26,10 @@ 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.heightIn
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -50,6 +52,8 @@ import com.android.systemui.qs.flags.QsDetailedView
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.Dimensions.GridMaxHeight
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
@@ -122,7 +126,9 @@ constructor(
// A sealed interface to represent the possible states of the `ShadeBody`
sealed interface ShadeBodyState {
data object Editing : ShadeBodyState
+
data object TileDetails : ShadeBodyState
+
data object Default : ShadeBodyState
}
@@ -149,9 +155,8 @@ fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
ShadeBodyState.Editing -> {
EditMode(
viewModel = viewModel.editModeViewModel,
- modifier = Modifier
- .fillMaxWidth()
- .padding(QuickSettingsShade.Dimensions.Padding),
+ modifier =
+ Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
)
}
ShadeBodyState.TileDetails -> {
@@ -182,22 +187,23 @@ fun SceneScope.QuickSettingsLayout(
.padding(
start = QuickSettingsShade.Dimensions.Padding,
end = QuickSettingsShade.Dimensions.Padding,
- top = QuickSettingsShade.Dimensions.Padding,
+ bottom = QuickSettingsShade.Dimensions.Padding / 2,
),
) {
+ Toolbar(viewModel.toolbarViewModelFactory)
BrightnessSliderContainer(
viewModel = viewModel.brightnessSliderViewModel,
modifier =
Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
)
- Box {
+ Box(
+ modifier =
+ Modifier.requiredHeightIn(max = GridMaxHeight)
+ .verticalNestedScrollToScene()
+ .verticalScroll(rememberScrollState())
+ ) {
GridAnchor()
- TileGrid(
- viewModel = viewModel.tileGridViewModel,
- modifier =
- Modifier.fillMaxWidth()
- .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
- )
+ TileGrid(viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth())
}
}
}
@@ -207,6 +213,6 @@ object QuickSettingsShade {
object Dimensions {
val Padding = 16.dp
val BrightnessSliderHeight = 64.dp
- val GridMaxHeight = 800.dp
+ val GridMaxHeight = 420.dp
}
}
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 a266e7eb44a1..c3dc84d0a12c 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
@@ -39,6 +39,7 @@ import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
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.SceneTransitions
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
@@ -78,6 +79,7 @@ fun SceneContainer(
sceneByKey: Map<SceneKey, Scene>,
overlayByKey: Map<OverlayKey, Overlay>,
initialSceneKey: SceneKey,
+ sceneTransitions: SceneTransitions,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
modifier: Modifier = Modifier,
@@ -87,7 +89,7 @@ fun SceneContainer(
MutableSceneTransitionLayoutState(
initialScene = initialSceneKey,
canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = SceneContainerTransitions,
+ transitions = sceneTransitions,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 6d03118645f3..0e35e1d83d6d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -45,6 +45,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -56,7 +57,7 @@ import kotlinx.coroutines.flow.StateFlow
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
+ private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -84,14 +85,26 @@ class ButtonComponent(
},
color =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.tertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.tertiaryContainer
+ }
} else {
- MaterialTheme.colorScheme.surface
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.surfaceContainerHigh
+ } else {
+ MaterialTheme.colorScheme.surface
+ }
},
shape = RoundedCornerShape(20.dp),
contentColor =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.onTertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.onPrimary
+ } else {
+ MaterialTheme.colorScheme.onTertiaryContainer
+ }
} else {
MaterialTheme.colorScheme.onSurface
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index bb2daecd3a25..2cd73040fa3f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -42,6 +42,7 @@ import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -51,7 +52,7 @@ import kotlinx.coroutines.flow.StateFlow
/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onCheckedChange: (isChecked: Boolean) -> Unit
+ private val onCheckedChange: (isChecked: Boolean) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -68,15 +69,29 @@ class ToggleButtonComponent(
BottomComponentButtonSurface {
val colors =
if (viewModel.isActive) {
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ )
+ }
} else {
- ButtonDefaults.buttonColors(
- containerColor = Color.Transparent,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ contentColor = MaterialTheme.colorScheme.onSurface,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
}
Button(
modifier =
@@ -93,7 +108,7 @@ class ToggleButtonComponent(
onClick = { onCheckedChange(!viewModel.isActive) },
shape = RoundedCornerShape(20.dp),
colors = colors,
- contentPadding = PaddingValues(0.dp)
+ contentPadding = PaddingValues(0.dp),
) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 581fb9d77c59..25892c5a75cc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -37,11 +37,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -51,8 +53,11 @@ import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.PlatformIconButton
import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -84,7 +89,11 @@ fun ColumnVolumeSliders(
val sliderPadding by topSliderPadding(isExpandable)
VolumeSlider(
- modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
+ modifier =
+ Modifier.thenIf(!Flags.volumeRedesign()) {
+ Modifier.padding(end = { sliderPadding.roundToPx() })
+ }
+ .fillMaxWidth(),
state = sliderState,
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
@@ -93,15 +102,29 @@ fun ColumnVolumeSliders(
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
+ button =
+ if (Flags.volumeRedesign()) {
+ {
+ ExpandButton(
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ )
+ }
+ } else {
+ null
+ },
)
- ExpandButton(
- modifier = Modifier.align(Alignment.CenterEnd),
- isExpanded = isExpanded,
- isExpandable = isExpandable,
- onExpandedChanged = onExpandedChanged,
- sliderColors = sliderColors,
- )
+ if (!Flags.volumeRedesign()) {
+ ExpandButtonLegacy(
+ modifier = Modifier.align(Alignment.CenterEnd),
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ sliderColors = sliderColors,
+ )
+ }
}
AnimatedVisibility(
visible = isExpanded || !isExpandable,
@@ -153,7 +176,7 @@ fun ColumnVolumeSliders(
}
@Composable
-private fun ExpandButton(
+private fun ExpandButtonLegacy(
isExpanded: Boolean,
isExpandable: Boolean,
onExpandedChanged: (Boolean) -> Unit,
@@ -200,6 +223,48 @@ private fun ExpandButton(
}
}
+@Composable
+private fun ExpandButton(
+ isExpanded: Boolean,
+ isExpandable: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val expandButtonStateDescription =
+ if (isExpanded) {
+ stringResource(R.string.volume_panel_expanded_sliders)
+ } else {
+ stringResource(R.string.volume_panel_collapsed_sliders)
+ }
+ AnimatedVisibility(
+ modifier = modifier,
+ visible = isExpandable,
+ enter = expandButtonEnterTransition(),
+ exit = expandButtonExitTransition(),
+ ) {
+ PlatformIconButton(
+ modifier =
+ Modifier.size(width = 48.dp, height = 40.dp).semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
+ onClick = { onExpandedChanged(!isExpanded) },
+ colors =
+ IconButtonDefaults.iconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ ),
+ iconResource =
+ if (isExpanded) {
+ R.drawable.ic_arrow_down_24dp
+ } else {
+ R.drawable.ic_arrow_up_24dp
+ },
+ contentDescription = null,
+ )
+ }
+}
+
private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 97ce429cf938..fa5f72bc0997 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -24,9 +24,18 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
+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.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.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
@@ -48,6 +57,7 @@ import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
+import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -61,11 +71,104 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
fun VolumeSlider(
state: SliderState,
onValueChange: (newValue: Float) -> Unit,
- onValueChangeFinished: (() -> Unit)? = null,
onIconTapped: () -> Unit,
+ sliderColors: PlatformSliderColors,
modifier: Modifier = Modifier,
+ hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ onValueChangeFinished: (() -> Unit)? = null,
+ button: (@Composable () -> Unit)? = null,
+) {
+ if (!Flags.volumeRedesign()) {
+ LegacyVolumeSlider(
+ state = state,
+ onValueChange = onValueChange,
+ onIconTapped = onIconTapped,
+ sliderColors = sliderColors,
+ onValueChangeFinished = onValueChangeFinished,
+ modifier = modifier,
+ hapticsViewModelFactory = hapticsViewModelFactory,
+ )
+ return
+ }
+
+ val value by valueState(state)
+ Column(modifier) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ state.icon?.let {
+ Icon(
+ icon = it,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.size(40.dp).padding(8.dp),
+ )
+ }
+ Text(
+ text = state.label,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.weight(1f).align(Alignment.CenterVertically),
+ )
+ button?.invoke()
+ }
+ Slider(
+ value = value,
+ valueRange = state.valueRange,
+ onValueChange = onValueChange,
+ onValueChangeFinished = onValueChangeFinished,
+ enabled = state.isEnabled,
+ modifier =
+ Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics {
+ if (state.isEnabled) {
+ contentDescription = state.label
+ state.a11yClickDescription?.let {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(it) {
+ onIconTapped()
+ true
+ }
+ )
+ }
+
+ state.a11yStateDescription?.let { stateDescription = it }
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ } else {
+ disabled()
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
+ }
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive,
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
+ )
+ }
+}
+
+@Composable
+private fun LegacyVolumeSlider(
+ state: SliderState,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
sliderColors: PlatformSliderColors,
hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ modifier: Modifier = Modifier,
+ onValueChangeFinished: (() -> Unit)? = null,
) {
val value by valueState(state)
val interactionSource = remember { MutableInteractionSource() }
@@ -178,7 +281,7 @@ private fun valueState(state: SliderState): State<Float> {
val shouldSkipAnimation =
prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
val value =
- if (shouldSkipAnimation) mutableFloatStateOf(state.value)
+ if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) }
else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
prevState = state
return value
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
index 4ae4eb875953..28226ff05ee9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
@@ -53,7 +53,7 @@ private enum class VolumeSliderContentComponent {
DisabledMessage,
}
-/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
+/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
@Composable
fun VolumeSliderContent(
label: String,
@@ -89,7 +89,7 @@ fun VolumeSliderContent(
}
}
},
- measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled)
+ measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled),
)
}
@@ -102,7 +102,7 @@ private class VolumeSliderContentMeasurePolicy(private val isEnabled: Boolean) :
override fun MeasureScope.measure(
measurables: List<Measurable>,
- constraints: Constraints
+ constraints: Constraints,
): MeasureResult {
val labelPlaceable =
measurables
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 0fc88b22a4d0..a4237f36ab58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -225,7 +225,7 @@ fun ElementScope<*>.animateElementColorAsState(value: Color, key: ValueKey): Ani
return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
}
-private object SharedColorType : SharedValueType<Color, ColorDelta> {
+internal object SharedColorType : SharedValueType<Color, ColorDelta> {
override val unspecifiedValue: Color = Color.Unspecified
override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
@@ -255,17 +255,17 @@ private object SharedColorType : SharedValueType<Color, ColorDelta> {
alpha = aOklab.alpha + b.alpha * bWeight,
colorSpace = ColorSpaces.Oklab,
)
- .convert(aOklab.colorSpace)
+ .convert(a.colorSpace)
}
}
/**
- * Represents the diff between two colors in the same color space.
+ * Represents the diff between two colors in the Oklab color space.
*
* Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
* is internal.
*/
-private class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
+internal class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
@Composable
internal fun <T> animateSharedValueAsState(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index cf0ba5196bad..2d589f37f3cb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:Suppress("NOTHING_TO_INLINE")
-
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
@@ -29,8 +27,11 @@ import com.android.compose.animation.scene.content.state.TransitionState.HasOver
import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
+import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.math.absoluteValue
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
internal interface DraggableHandler {
/**
@@ -97,68 +98,11 @@ internal class DraggableHandlerImpl(
internal val positionalThreshold
get() = with(layoutImpl.density) { 56.dp.toPx() }
- /**
- * Whether we should immediately intercept a gesture.
- *
- * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
- * indicating that the transition should be intercepted.
- */
- internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
- // We don't intercept the touch if we are not currently driving the transition.
- val dragController = dragController
- if (dragController?.isDrivingTransition != true) {
- return false
- }
-
- val swipeAnimation = dragController.swipeAnimation
-
- // Only intercept the current transition if one of the 2 swipes results is also a transition
- // between the same pair of contents.
- val swipes = computeSwipes(pointersDown)
- val fromContent = layoutImpl.content(swipeAnimation.currentContent)
- val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
- val currentScene = layoutImpl.state.currentScene
- val contentTransition = swipeAnimation.contentTransition
- return (upOrLeft != null &&
- contentTransition.isTransitioningBetween(
- fromContent.key,
- upOrLeft.toContent(currentScene),
- )) ||
- (downOrRight != null &&
- contentTransition.isTransitioningBetween(
- fromContent.key,
- downOrRight.toContent(currentScene),
- ))
- }
-
override fun onDragStarted(
pointersDown: PointersInfo.PointersDown?,
overSlop: Float,
): DragController {
- if (overSlop == 0f) {
- val oldDragController = dragController
- check(oldDragController != null && oldDragController.isDrivingTransition) {
- val isActive = oldDragController?.isDrivingTransition
- "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
- }
-
- // This [transition] was already driving the animation: simply take over it.
- // Stop animating and start from the current offset.
- val oldSwipeAnimation = oldDragController.swipeAnimation
-
- // We need to recompute the swipe results since this is a new gesture, and the
- // fromScene.userActions may have changed.
- val swipes = oldDragController.swipes
- swipes.updateSwipesResults(
- fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
- )
-
- // A new gesture should always create a new SwipeAnimation. This way there cannot be
- // different gestures controlling the same transition.
- val swipeAnimation = createSwipeAnimation(oldSwipeAnimation)
- return updateDragController(swipes, swipeAnimation)
- }
-
+ check(overSlop != 0f)
val swipes = computeSwipes(pointersDown)
val fromContent = layoutImpl.contentForUserActions()
@@ -183,7 +127,7 @@ internal class DraggableHandlerImpl(
return newDragController
}
- internal fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
+ private fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
val upOrLeftResult = swipes.upOrLeftResult
val downOrRightResult = swipes.downOrRightResult
val isUpOrLeft =
@@ -196,7 +140,7 @@ internal class DraggableHandlerImpl(
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
+ private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
return layoutImpl.swipeSourceDetector.source(
layoutSize = layoutImpl.lastSize,
position = startedPosition.round(),
@@ -250,9 +194,15 @@ private class DragControllerImpl(
private val draggableHandler: DraggableHandlerImpl,
val swipes: Swipes,
var swipeAnimation: SwipeAnimation<*>,
-) : DragController {
+) : DragController, SpaceVectorConverter by SpaceVectorConverter(draggableHandler.orientation) {
val layoutState = draggableHandler.layoutImpl.state
+ val overscrollableContent: OverscrollableContent =
+ when (draggableHandler.orientation) {
+ Orientation.Vertical -> draggableHandler.layoutImpl.verticalOverscrollableContent
+ Orientation.Horizontal -> draggableHandler.layoutImpl.horizontalOverscrollableContent
+ }
+
/**
* Whether this handle is active. If this returns false, calling [onDrag] and [onStop] will do
* nothing.
@@ -283,113 +233,75 @@ private class DragControllerImpl(
* @return the consumed delta
*/
override fun onDrag(delta: Float): Float {
- return onDrag(delta, swipeAnimation)
- }
-
- private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
- if (delta == 0f || !isDrivingTransition || swipeAnimation.isAnimatingOffset()) {
+ val initialAnimation = swipeAnimation
+ if (delta == 0f || !isDrivingTransition || initialAnimation.isAnimatingOffset()) {
return 0f
}
+ // swipeAnimation can change during the gesture, we want to always use the initial reference
+ // during the whole drag gesture.
+ return dragWithOverscroll(delta, animation = initialAnimation)
+ }
- val toContent = swipeAnimation.toContent
- val distance = swipeAnimation.distance()
- val previousOffset = swipeAnimation.dragOffset
- val desiredOffset = previousOffset + delta
-
- fun hasReachedToSceneUpOrLeft() =
- distance < 0 &&
- desiredOffset <= distance &&
- swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent
-
- fun hasReachedToSceneDownOrRight() =
- distance > 0 &&
- desiredOffset >= distance &&
- swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent
-
- // Considering accelerated swipe: Change fromContent in the case where the user quickly
- // swiped multiple times in the same direction to accelerate the transition from A => B then
- // B => C.
- //
- // TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging
- // twice before B has been reached
- val hasReachedToContent =
- swipeAnimation.currentContent == toContent &&
- (hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())
-
- val fromContent: ContentKey
- val currentTransitionOffset: Float
- val newOffset: Float
- val consumedDelta: Float
- if (hasReachedToContent) {
- // The new transition will start from the current toContent.
- fromContent = toContent
-
- // The current transition is completed (we have reached the full swipe distance).
- currentTransitionOffset = distance
-
- // The next transition will start with the remaining offset.
- newOffset = desiredOffset - distance
- consumedDelta = delta
- } else {
- fromContent = swipeAnimation.fromContent
- val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
-
- // Note: the distance could be negative if fromContent is above or to the left of
- // toContent.
- currentTransitionOffset =
- when {
- distance == DistanceUnspecified ||
- swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
- desiredOffset
-
- distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
- else -> desiredOffset.fastCoerceIn(distance, 0f)
- }
-
- // If there is a new transition, we will use the same offset
- newOffset = currentTransitionOffset
- consumedDelta = newOffset - previousOffset
- }
-
- swipeAnimation.dragOffset = currentTransitionOffset
-
- if (hasReachedToContent) {
- swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
+ private fun <T : ContentKey> dragWithOverscroll(
+ delta: Float,
+ animation: SwipeAnimation<T>,
+ ): Float {
+ require(delta != 0f) { "delta should not be 0" }
+ var overscrollEffect = overscrollableContent.currentOverscrollEffect
+
+ // If we're already overscrolling, continue with the current effect for a smooth finish.
+ if (overscrollEffect == null || !overscrollEffect.isInProgress) {
+ // Otherwise, determine the target content (toContent or fromContent) for the new
+ // overscroll effect based on the gesture's direction.
+ val content = animation.contentByDirection(delta)
+ overscrollEffect = overscrollableContent.applyOverscrollEffectOn(content)
}
- val result = swipes.findUserActionResult(directionOffset = newOffset)
- if (result == null) {
- onCancel(canChangeContent = true)
- return 0f
+ // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags.
+ if (!overscrollEffect.node.node.isAttached) {
+ return drag(delta, animation)
}
- val currentTransitionIrreversible =
- if (swipeAnimation.isUpOrLeft) {
- swipes.upOrLeftResult?.isIrreversible ?: false
- } else {
- swipes.downOrRightResult?.isIrreversible ?: false
- }
-
- val needNewTransition =
- !currentTransitionIrreversible &&
- (hasReachedToContent ||
- result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
- result.transitionKey != swipeAnimation.contentTransition.key)
+ return overscrollEffect
+ .applyToScroll(
+ delta = delta.toOffset(),
+ source = NestedScrollSource.UserInput,
+ performScroll = {
+ val preScrollAvailable = it.toFloat()
+ drag(preScrollAvailable, animation).toOffset()
+ },
+ )
+ .toFloat()
+ }
- if (needNewTransition) {
- // Make sure the current transition will finish to the right current scene.
- swipeAnimation.currentContent = fromContent
+ private fun <T : ContentKey> drag(delta: Float, animation: SwipeAnimation<T>): Float {
+ if (delta == 0f) return 0f
- val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
- newSwipeAnimation.dragOffset = newOffset
- updateTransition(newSwipeAnimation)
- }
+ val distance = animation.distance()
+ val previousOffset = animation.dragOffset
+ val desiredOffset = previousOffset + delta
+ val desiredProgress = animation.computeProgress(desiredOffset)
+
+ // Note: the distance could be negative if fromContent is above or to the left of toContent.
+ val newOffset =
+ when {
+ distance == DistanceUnspecified ||
+ animation.contentTransition.isWithinProgressRange(desiredProgress) ->
+ desiredOffset
+ distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
+ else -> desiredOffset.fastCoerceIn(distance, 0f)
+ }
- return consumedDelta
+ animation.dragOffset = newOffset
+ return newOffset - previousOffset
}
override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
- return onStop(velocity, canChangeContent, swipeAnimation)
+ // To ensure that any ongoing animation completes gracefully and avoids an undefined state,
+ // we execute the actual `onStop` logic in a non-cancellable context. This prevents the
+ // coroutine from being cancelled prematurely, which could interrupt the animation.
+ // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags.
+ return withContext(NonCancellable) { onStop(velocity, canChangeContent, swipeAnimation) }
}
private suspend fun <T : ContentKey> onStop(
@@ -440,7 +352,22 @@ private class DragControllerImpl(
fromContent
}
- return swipeAnimation.animateOffset(velocity, targetContent)
+ val overscrollEffect = overscrollableContent.applyOverscrollEffectOn(targetContent)
+
+ // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags.
+ if (!overscrollEffect.node.node.isAttached) {
+ return swipeAnimation.animateOffset(velocity, targetContent)
+ }
+
+ overscrollEffect.applyToFling(
+ velocity = velocity.toVelocity(),
+ performFling = {
+ val velocityLeft = it.toFloat()
+ swipeAnimation.animateOffset(velocityLeft, targetContent).toVelocity()
+ },
+ )
+
+ return velocity
}
/**
@@ -499,7 +426,9 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol
var upOrLeftResult: UserActionResult? = null
var downOrRightResult: UserActionResult? = null
- fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
+ private fun computeSwipesResults(
+ fromContent: Content
+ ): Pair<UserActionResult?, UserActionResult?> {
val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
return upOrLeftResult to downOrRightResult
@@ -564,48 +493,9 @@ internal class NestedScrollHandlerImpl(
.shouldEnableSwipes(draggableHandler.orientation)
}
- var isIntercepting = false
-
return PriorityNestedScrollConnection(
orientation = draggableHandler.orientation,
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
- val pointersDown: PointersInfo.PointersDown? =
- when (val info = pointersInfoOwner.pointersInfo()) {
- PointersInfo.MouseWheel -> {
- // Do not support mouse wheel interactions
- return@PriorityNestedScrollConnection false
- }
-
- is PointersInfo.PointersDown -> info
- null -> null
- }
-
- canChangeScene =
- if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
-
- val canInterceptSwipeTransition =
- canChangeScene &&
- offsetAvailable != 0f &&
- draggableHandler.shouldImmediatelyIntercept(pointersDown)
- if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
-
- val layoutImpl = draggableHandler.layoutImpl
- val threshold = layoutImpl.transitionInterceptionThreshold
- val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold)
- if (hasSnappedToIdle) {
- // If the current swipe transition is closed to 0f or 1f, then we want to
- // interrupt the transition (snapping it to Idle) and scroll the list.
- return@PriorityNestedScrollConnection false
- }
-
- lastPointersDown = pointersDown
-
- // If the current swipe transition is *not* closed to 0f or 1f, then we want the
- // scroll events to intercept the current transition to continue the scene
- // transition.
- isIntercepting = true
- true
- },
+ canStartPreScroll = { _, _, _ -> false },
canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
val behavior: NestedScrollBehavior =
when {
@@ -629,29 +519,22 @@ internal class NestedScrollHandlerImpl(
}
lastPointersDown = pointersDown
- val canStart =
- when (behavior) {
- NestedScrollBehavior.EdgeNoPreview -> {
- canChangeScene = isZeroOffset
- isZeroOffset && shouldEnableSwipes()
- }
-
- NestedScrollBehavior.EdgeWithPreview -> {
- canChangeScene = isZeroOffset
- shouldEnableSwipes()
- }
+ when (behavior) {
+ NestedScrollBehavior.EdgeNoPreview -> {
+ canChangeScene = isZeroOffset
+ isZeroOffset && shouldEnableSwipes()
+ }
- NestedScrollBehavior.EdgeAlways -> {
- canChangeScene = true
- shouldEnableSwipes()
- }
+ NestedScrollBehavior.EdgeWithPreview -> {
+ canChangeScene = isZeroOffset
+ shouldEnableSwipes()
}
- if (canStart) {
- isIntercepting = false
+ NestedScrollBehavior.EdgeAlways -> {
+ canChangeScene = true
+ shouldEnableSwipes()
+ }
}
-
- canStart
},
canStartPostFling = { velocityAvailable ->
val behavior: NestedScrollBehavior =
@@ -676,19 +559,14 @@ internal class NestedScrollHandlerImpl(
}
lastPointersDown = pointersDown
- val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
- if (canStart) {
- isIntercepting = false
- }
-
- canStart
+ behavior.canStartOnPostFling && shouldEnableSwipes()
},
onStart = { firstScroll ->
scrollController(
dragController =
draggableHandler.onDragStarted(
pointersDown = lastPointersDown,
- overSlop = if (isIntercepting) 0f else firstScroll,
+ overSlop = firstScroll,
),
canChangeScene = canChangeScene,
pointersInfoOwner = pointersInfoOwner,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index eb2a01632095..e819bfd18578 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -53,10 +53,10 @@ import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
import com.android.compose.animation.scene.transformation.InterpolatedPropertyTransformation
import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.drawInContainer
+import com.android.compose.ui.util.IntIndexedMap
import com.android.compose.ui.util.lerp
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -70,6 +70,14 @@ internal class Element(val key: ElementKey) {
val stateByContent = SnapshotStateMap<ContentKey, State>()
/**
+ * A sorted map of nesting depth (key) to content key (value). For shared elements it is used to
+ * determine which content this element should be rendered by. The nesting depth refers to the
+ * number of STLs nested within each other, starting at 0 for the parent STL and increasing by
+ * one for each nested [NestedSceneTransitionLayout].
+ */
+ val renderAuthority = IntIndexedMap<ContentKey>()
+
+ /**
* The last transition that was used when computing the state (size, position and alpha) of this
* element in any content, or `null` if it was last laid out when idle.
*/
@@ -232,9 +240,8 @@ internal class ElementNode(
private val element: Element
get() = _element!!
- private var _stateInContent: Element.State? = null
private val stateInContent: Element.State
- get() = _stateInContent!!
+ get() = element.stateByContent.getValue(content.key)
override val traverseKey: Any = ElementTraverseKey
@@ -248,9 +255,13 @@ internal class ElementNode(
val element =
layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
_element = element
- _stateInContent =
- element.stateByContent[content.key]
- ?: Element.State(content.key).also { element.stateByContent[content.key] = it }
+ addToRenderAuthority(element)
+ if (!element.stateByContent.contains(content.key)) {
+ val elementState = Element.State(content.key)
+ element.stateByContent[content.key] = elementState
+
+ layoutImpl.ancestorContentKeys.forEach { element.stateByContent[it] = elementState }
+ }
}
private fun addNodeToContentState() {
@@ -272,8 +283,20 @@ internal class ElementNode(
removeNodeFromContentState()
maybePruneMaps(layoutImpl, element, stateInContent)
+ removeFromRenderAuthority()
_element = null
- _stateInContent = null
+ }
+
+ private fun addToRenderAuthority(element: Element) {
+ val nestingDepth = layoutImpl.ancestorContentKeys.size
+ element.renderAuthority[nestingDepth] = content.key
+ }
+
+ private fun removeFromRenderAuthority() {
+ val nestingDepth = layoutImpl.ancestorContentKeys.size
+ if (element.renderAuthority[nestingDepth] == content.key) {
+ element.renderAuthority.remove(nestingDepth)
+ }
}
private fun removeNodeFromContentState() {
@@ -346,15 +369,17 @@ internal class ElementNode(
val elementState = elementState(layoutImpl, element, currentTransitionStates)
if (elementState == null) {
// If the element is not part of any transition, place it normally in its idle scene.
+ // This is the case if for example a transition between two overlays is ongoing where
+ // sharedElement isn't part of either but the element is still rendered as part of
+ // the underlying scene that is currently not being transitioned.
val currentState = currentTransitionStates.last()
- val placeInThisContent =
+ val shouldPlaceInThisContent =
elementContentWhenIdle(
layoutImpl,
currentState,
isInContent = { it in element.stateByContent },
) == content.key
-
- return if (placeInThisContent) {
+ return if (shouldPlaceInThisContent) {
placeNormally(measurable, constraints)
} else {
doNotPlace(measurable, constraints)
@@ -536,7 +561,9 @@ internal class ElementNode(
stateInContent.clearLastPlacementValues()
traverseDescendants(ElementTraverseKey) { node ->
- (node as ElementNode)._stateInContent?.clearLastPlacementValues()
+ if ((node as ElementNode)._element != null) {
+ node.stateInContent.clearLastPlacementValues()
+ }
TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal
}
}
@@ -569,22 +596,30 @@ internal class ElementNode(
element: Element,
stateInContent: Element.State,
) {
- // If element is not composed in this content anymore, remove the content values. This
- // works because [onAttach] is called before [onDetach], so if an element is moved from
- // the UI tree we will first add the new code location then remove the old one.
- if (
- stateInContent.nodes.isEmpty() &&
- element.stateByContent[stateInContent.content] == stateInContent
- ) {
- element.stateByContent.remove(stateInContent.content)
-
- // If the element is not composed in any content, remove it from the elements map.
+ fun pruneForContent(contentKey: ContentKey) {
+ // If element is not composed in this content anymore, remove the content values.
+ // This works because [onAttach] is called before [onDetach], so if an element is
+ // moved from the UI tree we will first add the new code location then remove the
+ // old one.
if (
- element.stateByContent.isEmpty() && layoutImpl.elements[element.key] == element
+ stateInContent.nodes.isEmpty() &&
+ element.stateByContent[contentKey] == stateInContent
) {
- layoutImpl.elements.remove(element.key)
+ element.stateByContent.remove(contentKey)
+
+ // If the element is not composed in any content, remove it from the elements
+ // map.
+ if (
+ element.stateByContent.isEmpty() &&
+ layoutImpl.elements[element.key] == element
+ ) {
+ layoutImpl.elements.remove(element.key)
+ }
}
}
+
+ pruneForContent(stateInContent.content)
+ layoutImpl.ancestorContentKeys.forEach { content -> pruneForContent(content) }
}
}
}
@@ -890,12 +925,13 @@ private fun shouldPlaceElement(
val transition =
when (elementState) {
is TransitionState.Idle -> {
- return content ==
- elementContentWhenIdle(
- layoutImpl,
- elementState,
- isInContent = { it in element.stateByContent },
- )
+ return element.shouldBeRenderedBy(content) &&
+ content ==
+ elementContentWhenIdle(
+ layoutImpl,
+ elementState,
+ isInContent = { it in element.stateByContent },
+ )
}
is TransitionState.Transition -> elementState
}
@@ -925,76 +961,7 @@ private fun shouldPlaceElement(
return true
}
- return shouldPlaceOrComposeSharedElement(
- layoutImpl,
- content,
- element.key,
- transition,
- isInContent = { it in element.stateByContent },
- )
-}
-
-internal inline fun shouldPlaceOrComposeSharedElement(
- layoutImpl: SceneTransitionLayoutImpl,
- content: ContentKey,
- element: ElementKey,
- transition: TransitionState.Transition,
- isInContent: (ContentKey) -> Boolean,
-): Boolean {
- val overscrollContent = transition.currentOverscrollSpec?.content
- if (overscrollContent != null) {
- return when (transition) {
- // If we are overscrolling between scenes, only place/compose the element in the
- // overscrolling scene.
- is TransitionState.Transition.ChangeScene -> content == overscrollContent
-
- // If we are overscrolling an overlay, place/compose the element if [content] is the
- // overscrolling content or if [content] is the current scene and the overscrolling
- // overlay does not contain the element.
- is TransitionState.Transition.ReplaceOverlay,
- is TransitionState.Transition.ShowOrHideOverlay ->
- content == overscrollContent ||
- (content == transition.currentScene && !isInContent(overscrollContent))
- }
- }
-
- val scenePicker = element.contentPicker
- val pickedScene =
- scenePicker.contentDuringTransition(
- element = element,
- transition = transition,
- fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
- toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
- )
-
- return pickedScene == content
-}
-
-private fun isSharedElementEnabled(
- element: ElementKey,
- transition: TransitionState.Transition,
-): Boolean {
- return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
-}
-
-internal fun sharedElementTransformation(
- element: ElementKey,
- transition: TransitionState.Transition,
-): TransformationWithRange<SharedElementTransformation>? {
- val transformationSpec = transition.transformationSpec
- val sharedInFromContent =
- transformationSpec.transformations(element, transition.fromContent).shared
- val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
-
- // The sharedElement() transformation must either be null or be the same in both contents.
- if (sharedInFromContent != sharedInToContent) {
- error(
- "Different sharedElement() transformations matched $element " +
- "(from=$sharedInFromContent to=$sharedInToContent)"
- )
- }
-
- return sharedInFromContent
+ return shouldPlaceSharedElement(layoutImpl, content, element.key, transition)
}
/**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 509a16c5a704..17510c732e65 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -196,18 +196,54 @@ private fun shouldComposeMovableElement(
is TransitionState.Transition -> {
// During transitions, always compose movable elements in the scene picked by their
// content picker.
- val contents = element.contentPicker.contents
- shouldPlaceOrComposeSharedElement(
+ shouldComposeMoveableElement(
layoutImpl,
content,
element,
elementState,
- isInContent = { contents.contains(it) },
+ element.contentPicker.contents,
)
}
}
}
+private fun shouldComposeMoveableElement(
+ layoutImpl: SceneTransitionLayoutImpl,
+ content: ContentKey,
+ elementKey: ElementKey,
+ transition: TransitionState.Transition,
+ containingContents: Set<ContentKey>,
+): Boolean {
+ val overscrollContent = transition.currentOverscrollSpec?.content
+ if (overscrollContent != null) {
+ return when (transition) {
+ // If we are overscrolling between scenes, only place/compose the element in the
+ // overscrolling scene.
+ is TransitionState.Transition.ChangeScene -> content == overscrollContent
+
+ // If we are overscrolling an overlay, place/compose the element if [content] is the
+ // overscrolling content or if [content] is the current scene and the overscrolling
+ // overlay does not contain the element.
+ is TransitionState.Transition.ReplaceOverlay,
+ is TransitionState.Transition.ShowOrHideOverlay ->
+ content == overscrollContent ||
+ (content == transition.currentScene &&
+ !containingContents.contains(overscrollContent))
+ }
+ }
+
+ val scenePicker = elementKey.contentPicker
+ val pickedScene =
+ scenePicker.contentDuringTransition(
+ element = elementKey,
+ transition = transition,
+ fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+ toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+ )
+
+ return pickedScene == content
+}
+
private fun movableElementState(
element: MovableElementKey,
transitionStates: List<TransitionState>,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 59ac68bd0299..f5f01d4d1a35 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -80,7 +80,6 @@ import kotlinx.coroutines.launch
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -89,7 +88,6 @@ internal fun Modifier.multiPointerDraggable(
this.then(
MultiPointerDraggableElement(
orientation,
- startDragImmediately,
onDragStarted,
onFirstPointerDown,
swipeDetector,
@@ -99,7 +97,6 @@ internal fun Modifier.multiPointerDraggable(
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
private val onDragStarted:
(pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
private val onFirstPointerDown: () -> Unit,
@@ -109,7 +106,6 @@ private data class MultiPointerDraggableElement(
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
orientation = orientation,
- startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
onFirstPointerDown = onFirstPointerDown,
swipeDetector = swipeDetector,
@@ -118,7 +114,6 @@ private data class MultiPointerDraggableElement(
override fun update(node: MultiPointerDraggableNode) {
node.orientation = orientation
- node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
node.onFirstPointerDown = onFirstPointerDown
node.swipeDetector = swipeDetector
@@ -127,16 +122,11 @@ private data class MultiPointerDraggableElement(
internal class MultiPointerDraggableNode(
orientation: Orientation,
- var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
var onFirstPointerDown: () -> Unit,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
-) :
- DelegatingNode(),
- PointerInputModifierNode,
- CompositionLocalConsumerModifierNode,
- SpaceVectorConverter {
+) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode {
private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
private val velocityTracker = VelocityTracker()
@@ -151,13 +141,13 @@ internal class MultiPointerDraggableNode(
private var converter = SpaceVectorConverter(orientation)
- override fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
+ fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
- override fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
+ fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
- override fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() }
+ fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() }
- override fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() }
+ fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() }
var orientation: Orientation = orientation
set(value) {
@@ -297,7 +287,6 @@ internal class MultiPointerDraggableNode(
try {
detectDragGestures(
orientation = orientation,
- startDragImmediately = startDragImmediately,
onDragStart = { pointersDown, overSlop ->
onDragStarted(pointersDown, overSlop)
},
@@ -438,13 +427,11 @@ internal class MultiPointerDraggableNode(
* Detect drag gestures in the given [orientation].
*
* This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
- * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
- * 1) starting the gesture immediately without requiring a drag >= touch slope;
- * 2) passing the number of pointers down to [onDragStart].
+ * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing
+ * the number of pointers down to [onDragStart].
*/
private suspend fun AwaitPointerEventScope.detectDragGestures(
orientation: Orientation,
- startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
onDrag: (controller: DragController, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
@@ -470,71 +457,49 @@ internal class MultiPointerDraggableNode(
.first()
var overSlop = 0f
- var lastPointersDown: PointersInfo.PointersDown =
+ val onSlopReached = { change: PointerInputChange, over: Float ->
+ if (swipeDetector.detectSwipe(change)) {
+ change.consume()
+ overSlop = over
+ }
+ }
+
+ // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
+ // is public.
+ val drag =
+ when (orientation) {
+ Orientation.Horizontal ->
+ awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+ Orientation.Vertical ->
+ awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+ } ?: return
+
+ val lastPointersDown =
checkNotNull(pointersInfo()) {
"We should have pointers down, last event: $currentEvent"
}
as PointersInfo.PointersDown
-
- val drag =
- if (startDragImmediately(lastPointersDown)) {
- consumablePointer.consume()
- consumablePointer
- } else {
- val onSlopReached = { change: PointerInputChange, over: Float ->
- if (swipeDetector.detectSwipe(change)) {
- change.consume()
- overSlop = over
- }
- }
-
- // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
- // is public.
- val drag =
- when (orientation) {
- Orientation.Horizontal ->
- awaitHorizontalTouchSlopOrCancellation(
- consumablePointer.id,
- onSlopReached,
- )
- Orientation.Vertical ->
- awaitVerticalTouchSlopOrCancellation(
- consumablePointer.id,
- onSlopReached,
- )
- } ?: return
-
- lastPointersDown =
- checkNotNull(pointersInfo()) {
- "We should have pointers down, last event: $currentEvent"
- }
- as PointersInfo.PointersDown
- // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
- // the touch slop. However, the overSlop we pass to onDragStarted() is used to
- // compute the direction we are dragging in, so overSlop should never be 0f unless
- // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
- // true).
- if (overSlop == 0f) {
- // If the user drags in the opposite direction, the delta becomes zero because
- // we return to the original point. Therefore, we should use the previous event
- // to calculate the direction.
- val delta = (drag.position - drag.previousPosition).toFloat()
- check(delta != 0f) {
- buildString {
- append("delta is equal to 0 ")
- append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
- append("consumablePointer.position ${consumablePointer.position} ")
- append("drag.position ${drag.position} ")
- append("drag.previousPosition ${drag.previousPosition}")
- }
- }
- overSlop = delta.sign
+ // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
+ // the touch slop. However, the overSlop we pass to onDragStarted() is used to
+ // compute the direction we are dragging in, so overSlop should never be 0f.
+ if (overSlop == 0f) {
+ // If the user drags in the opposite direction, the delta becomes zero because
+ // we return to the original point. Therefore, we should use the previous event
+ // to calculate the direction.
+ val delta = (drag.position - drag.previousPosition).toFloat()
+ check(delta != 0f) {
+ buildString {
+ append("delta is equal to 0 ")
+ append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
+ append("consumablePointer.position ${consumablePointer.position} ")
+ append("drag.position ${drag.position} ")
+ append("drag.previousPosition ${drag.previousPosition}")
}
- drag
}
+ overSlop = delta.sign
+ }
val controller = onDragStart(lastPointersDown, overSlop)
-
val successful: Boolean
try {
onDrag(controller, overSlop)
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 d3ddb5003469..bf7e8e823658 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
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
@@ -35,6 +36,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.effect.ContentOverscrollEffect
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -68,7 +70,7 @@ fun SceneTransitionLayout(
swipeDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
- builder,
+ builder = builder,
)
}
@@ -261,14 +263,74 @@ interface BaseContentScope : ElementStateScope {
* lists keep a constant size during transitions even if its elements are growing/shrinking.
*/
fun Modifier.noResizeDuringTransitions(): Modifier
+
+ /**
+ * A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore
+ * enabling sharedElement transitions between them.
+ */
+ // TODO(b/380070506): Add more parameters when default params are supported in Kotlin 2.0.21
+ @Composable
+ fun NestedSceneTransitionLayout(
+ state: SceneTransitionLayoutState,
+ modifier: Modifier,
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ )
}
+@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope"))
typealias SceneScope = ContentScope
@Stable
@ElementDsl
interface ContentScope : BaseContentScope {
/**
+ * The overscroll effect applied to the content in the vertical direction. This can be used to
+ * customize how the content behaves when the scene is over scrolled.
+ *
+ * For example, you can use it with the `Modifier.overscroll()` modifier:
+ * ```kotlin
+ * @Composable
+ * fun ContentScope.MyScene() {
+ * Box(
+ * modifier = Modifier
+ * // Apply the effect
+ * .overscroll(verticalOverscrollEffect)
+ * ) {
+ * // ... your content ...
+ * }
+ * }
+ * ```
+ *
+ * Or you can read the `overscrollDistance` value directly, if you need some custom overscroll
+ * behavior:
+ * ```kotlin
+ * @Composable
+ * fun ContentScope.MyScene() {
+ * Box(
+ * modifier = Modifier
+ * .graphicsLayer {
+ * // Translate half of the overscroll
+ * translationY = verticalOverscrollEffect.overscrollDistance * 0.5f
+ * }
+ * ) {
+ * // ... your content ...
+ * }
+ * }
+ * ```
+ *
+ * @see horizontalOverscrollEffect
+ */
+ val verticalOverscrollEffect: ContentOverscrollEffect
+
+ /**
+ * The overscroll effect applied to the content in the horizontal direction. This can be used to
+ * customize how the content behaves when the scene is over scrolled.
+ *
+ * @see verticalOverscrollEffect
+ */
+ val horizontalOverscrollEffect: ContentOverscrollEffect
+
+ /**
* Animate some value at the content level.
*
* @param value the value of this shared value in the current content.
@@ -540,12 +602,6 @@ sealed class UserActionResult(
* bigger than 100% when the user released their finger. `
*/
open val requiresFullDistanceSwipe: Boolean,
-
- /**
- * Whether swiping back in the opposite direction past the origin point of the swipe can replace
- * the action with the action for the opposite direction.
- */
- open val isIrreversible: Boolean = false,
) {
internal abstract fun toContent(currentScene: SceneKey): ContentKey
@@ -555,7 +611,6 @@ sealed class UserActionResult(
val toScene: SceneKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
- override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = toScene
}
@@ -565,7 +620,6 @@ sealed class UserActionResult(
val overlay: OverlayKey,
override val transitionKey: TransitionKey? = null,
override val requiresFullDistanceSwipe: Boolean = false,
- override val isIrreversible: Boolean = false,
) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
override fun toContent(currentScene: SceneKey): ContentKey = overlay
}
@@ -608,14 +662,7 @@ sealed class UserActionResult(
* the user released their finger.
*/
requiresFullDistanceSwipe: Boolean = false,
-
- /**
- * Whether swiping back in the opposite direction past the origin point of the swipe can
- * replace the action with the action for the opposite direction.
- */
- isIrreversible: Boolean = false,
- ): UserActionResult =
- ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
+ ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
/** A [UserActionResult] that shows [toOverlay]. */
operator fun invoke(
@@ -677,6 +724,9 @@ internal fun SceneTransitionLayoutForTesting(
swipeDetector: SwipeDetector = DefaultSwipeDetector,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
+ sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
+ ancestorContentKeys: List<ContentKey> = emptyList(),
+ lookaheadScope: LookaheadScope? = null,
builder: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
@@ -691,6 +741,9 @@ internal fun SceneTransitionLayoutForTesting(
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = builder,
animationScope = animationScope,
+ elements = sharedElementMap,
+ ancestorContentKeys = ancestorContentKeys,
+ lookaheadScope = lookaheadScope,
)
.also { onLayoutImpl?.invoke(it) }
}
@@ -706,6 +759,24 @@ internal fun SceneTransitionLayoutForTesting(
" that was used when creating it, which is not supported"
)
}
+ if (layoutImpl.elements != sharedElementMap) {
+ error(
+ "This SceneTransitionLayout was bound to a different elements map that was used " +
+ "when creating it, which is not supported"
+ )
+ }
+ if (layoutImpl.ancestorContentKeys != ancestorContentKeys) {
+ error(
+ "This SceneTransitionLayout was bound to a different ancestorContents that was " +
+ "used when creating it, which is not supported"
+ )
+ }
+ if (lookaheadScope != null && layoutImpl.lookaheadScope != lookaheadScope) {
+ error(
+ "This SceneTransitionLayout was bound to a different lookaheadScope that was " +
+ "used when creating it, which is not supported"
+ )
+ }
layoutImpl.density = density
layoutImpl.layoutDirection = layoutDirection
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 b916b0b45e41..d7bac147d8f2 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
@@ -49,8 +49,10 @@ import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.Overlay
import com.android.compose.animation.scene.content.Scene
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.effect.GestureEffect
import com.android.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** The type for the content of movable elements. */
internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit
@@ -70,7 +72,39 @@ internal class SceneTransitionLayoutImpl(
* animations.
*/
internal val animationScope: CoroutineScope,
+
+ /**
+ * The map of [Element]s.
+ *
+ * Important: [Element]s from this map should never be accessed during composition because the
+ * Elements are added when the associated Modifier.element() node is attached to the Modifier
+ * tree, i.e. after composition.
+ */
+ internal val elements: MutableMap<ElementKey, Element> = mutableMapOf(),
+
+ /**
+ * When this STL is a [NestedSceneTransitionLayout], this is a list of [ContentKey]s of where
+ * this STL is composed in within its ancestors.
+ *
+ * The root STL holds an emptyList. With each nesting level the parent is supposed to add
+ * exactly one scene to the list, therefore the size of this list is equal to the nesting depth
+ * of this STL.
+ *
+ * This is used to know in which content of the ancestors a sharedElement appears in.
+ */
+ internal val ancestorContentKeys: List<ContentKey> = emptyList(),
+ lookaheadScope: LookaheadScope? = null,
) {
+
+ /**
+ * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the
+ * layout. For [NestedSceneTransitionLayout]s this scope is the scope of the root STL, such that
+ * offset computations can be shared among all children.
+ */
+ private var _lookaheadScope: LookaheadScope? = lookaheadScope
+ internal val lookaheadScope: LookaheadScope
+ get() = _lookaheadScope!!
+
/**
* The map of [Scene]s.
*
@@ -89,15 +123,6 @@ internal class SceneTransitionLayoutImpl(
get() = _overlays ?: SnapshotStateMap<OverlayKey, Overlay>().also { _overlays = it }
/**
- * The map of [Element]s.
- *
- * Important: [Element]s from this map should never be accessed during composition because the
- * Elements are added when the associated Modifier.element() node is attached to the Modifier
- * tree, i.e. after composition.
- */
- internal val elements = mutableMapOf<ElementKey, Element>()
-
- /**
* The map of contents of movable elements.
*
* Note that given that this map is mutated directly during a composition, it has to be a
@@ -111,6 +136,18 @@ internal class SceneTransitionLayoutImpl(
_movableContents = it
}
+ internal var horizontalOverscrollableContent =
+ OverscrollableContent(
+ animationScope = animationScope,
+ overscrollEffect = { content(it).scope.horizontalOverscrollGestureEffect },
+ )
+
+ internal var verticalOverscrollableContent =
+ OverscrollableContent(
+ animationScope = animationScope,
+ overscrollEffect = { content(it).scope.verticalOverscrollGestureEffect },
+ )
+
/**
* The different values of a shared value keyed by a a [ValueKey] and the different elements and
* contents it is associated to.
@@ -138,13 +175,6 @@ internal class SceneTransitionLayoutImpl(
_userActionDistanceScope = it
}
- /**
- * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the
- * layout.
- */
- internal lateinit var lookaheadScope: LookaheadScope
- private set
-
internal var lastSize: IntSize = IntSize.Zero
init {
@@ -347,7 +377,12 @@ internal class SceneTransitionLayoutImpl(
.then(LayoutElement(layoutImpl = this))
) {
LookaheadScope {
- lookaheadScope = this
+ if (_lookaheadScope == null) {
+ // We can't init this in a SideEffect as other NestedSTLs are already calling
+ // this during composition. However, when composition is canceled
+ // SceneTransitionLayoutImpl is discarded as well. So it's fine to do this here.
+ _lookaheadScope = this
+ }
BackHandler()
Scenes()
@@ -540,3 +575,23 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
return layout(width, height) { placeable.place(0, 0) }
}
}
+
+internal class OverscrollableContent(
+ private val animationScope: CoroutineScope,
+ private val overscrollEffect: (ContentKey) -> GestureEffect,
+) {
+ private var currentContent: ContentKey? = null
+ var currentOverscrollEffect: GestureEffect? = null
+
+ fun applyOverscrollEffectOn(contentKey: ContentKey): GestureEffect {
+ if (currentContent == contentKey) return currentOverscrollEffect!!
+
+ currentOverscrollEffect?.apply { animationScope.launch { ensureApplyToFlingIsCalled() } }
+
+ // We are wrapping the overscroll effect.
+ val overscrollEffect = overscrollEffect(contentKey)
+ currentContent = contentKey
+ currentOverscrollEffect = overscrollEffect
+ return overscrollEffect
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 4bccac1e3ba0..86c5fd824d8f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -28,7 +28,6 @@ import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
@@ -528,39 +527,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
}
- /**
- * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
- * to the closest scene.
- *
- * Important: Snapping to the closest scene will instantly finish *all* ongoing transitions,
- * only the progress of the last transition will be checked.
- *
- * @return true if snapped to the closest scene.
- */
- internal fun snapToIdleIfClose(threshold: Float): Boolean {
- val transition = currentTransition ?: return false
- val progress = transition.progress
-
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- fun finishAllTransitions() {
- // Force finish all transitions.
- while (currentTransitions.isNotEmpty()) {
- finishTransition(transitionStates[0] as TransitionState.Transition)
- }
- }
-
- val shouldSnap =
- (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
- (isProgressCloseTo(1f) && transition.isToCurrentContent())
- return if (shouldSnap) {
- finishAllTransitions()
- true
- } else {
- false
- }
- }
-
override fun showOverlay(
overlay: OverlayKey,
animationScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
new file mode 100644
index 000000000000..599a152a23bd
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
+
+/**
+ * Whether this element should be rendered by the given [content]. This method returns true only for
+ * exactly one content at any given time.
+ */
+internal fun Element.shouldBeRenderedBy(content: ContentKey): Boolean {
+ // The current strategy is that always the content with the lowest nestingDepth has authority.
+ // This content is supposed to render the shared element because this is also the level at which
+ // the transition is running. If the [renderAuthority.size] is 1 it means that that this element
+ // is currently composed only in one nesting level, which means that the render authority
+ // is determined by "classic" shared element code.
+ return renderAuthority.size == 1 || renderAuthority.first() == content
+}
+
+/**
+ * Whether this element is currently composed in multiple [SceneTransitionLayout]s.
+ *
+ * Note: Shared elements across [NestedSceneTransitionLayout]s side-by-side are not supported.
+ */
+internal fun Element.isPresentInMultipleStls(): Boolean {
+ return renderAuthority.size > 1
+}
+
+internal fun shouldPlaceSharedElement(
+ layoutImpl: SceneTransitionLayoutImpl,
+ content: ContentKey,
+ elementKey: ElementKey,
+ transition: TransitionState.Transition,
+): Boolean {
+ val element = layoutImpl.elements.getValue(elementKey)
+ if (element.isPresentInMultipleStls()) {
+ // If the element is present in multiple STLs we require the highest STL to render it and
+ // we don't want contentPicker to potentially return false for the highest STL.
+ return element.shouldBeRenderedBy(content)
+ }
+
+ val overscrollContent = transition.currentOverscrollSpec?.content
+ if (overscrollContent != null) {
+ return when (transition) {
+ // If we are overscrolling between scenes, only place/compose the element in the
+ // overscrolling scene.
+ is TransitionState.Transition.ChangeScene -> content == overscrollContent
+
+ // If we are overscrolling an overlay, place/compose the element if [content] is the
+ // overscrolling content or if [content] is the current scene and the overscrolling
+ // overlay does not contain the element.
+ is TransitionState.Transition.ReplaceOverlay,
+ is TransitionState.Transition.ShowOrHideOverlay ->
+ content == overscrollContent ||
+ (content == transition.currentScene &&
+ overscrollContent !in element.stateByContent)
+ }
+ }
+
+ val scenePicker = elementKey.contentPicker
+ val pickedScene =
+ scenePicker.contentDuringTransition(
+ element = elementKey,
+ transition = transition,
+ fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+ toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+ )
+
+ return pickedScene == content
+}
+
+internal fun isSharedElementEnabled(
+ element: ElementKey,
+ transition: TransitionState.Transition,
+): Boolean {
+ return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+ element: ElementKey,
+ transition: TransitionState.Transition,
+): TransformationWithRange<SharedElementTransformation>? {
+ val transformationSpec = transition.transformationSpec
+ val sharedInFromContent =
+ transformationSpec.transformations(element, transition.fromContent).shared
+ val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
+
+ // The sharedElement() transformation must either be null or be the same in both contents.
+ if (sharedInFromContent != sharedInToContent) {
+ error(
+ "Different sharedElement() transformations matched $element " +
+ "(from=$sharedInFromContent to=$sharedInToContent)"
+ )
+ }
+
+ return sharedInFromContent
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index ae235e5097af..35cdf81e8c14 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -313,6 +313,17 @@ internal class SwipeAnimation<T : ContentKey>(
fun isAnimatingOffset(): Boolean = offsetAnimation != null
+ /** Get the [ContentKey] ([fromContent] or [toContent]) associated to the current [direction] */
+ fun contentByDirection(direction: Float): T {
+ require(direction != 0f) { "Cannot find a content in this direction: $direction" }
+ val isDirectionToContent = (isUpOrLeft && direction < 0) || (!isUpOrLeft && direction > 0)
+ return if (isDirectionToContent) {
+ toContent
+ } else {
+ fromContent
+ }
+ }
+
/**
* Animate the offset to a [targetContent], using the [initialVelocity] and an optional [spec]
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 5ab306a63a7e..6ef8b86cf72a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -149,7 +149,6 @@ private class SwipeToSceneNode(
delegate(
MultiPointerDraggableNode(
orientation = draggableHandler.orientation,
- startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
onFirstPointerDown = ::onFirstPointerDown,
swipeDetector = swipeDetector,
@@ -198,21 +197,6 @@ private class SwipeToSceneNode(
) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
-
- private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
- // Immediately start the drag if the user can't swipe in the other direction and the gesture
- // handler can intercept it.
- return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
- }
-
- private fun canOppositeSwipe(): Boolean {
- val oppositeOrientation =
- when (draggableHandler.orientation) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
- return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
- }
}
/** Find the [ScrollBehaviorOwner] for the current orientation. */
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 255a16c6de6b..152f05eb5cc7 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
@@ -17,12 +17,14 @@
package com.android.compose.animation.scene.content
import android.annotation.SuppressLint
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
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.approachLayout
@@ -41,13 +43,18 @@ import com.android.compose.animation.scene.MovableElement
import com.android.compose.animation.scene.MovableElementContentScope
import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SceneTransitionLayoutScope
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.SharedValueType
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateSharedValueAsState
+import com.android.compose.animation.scene.effect.GestureEffect
+import com.android.compose.animation.scene.effect.OffsetOverscrollEffect
+import com.android.compose.animation.scene.effect.VisualEffect
import com.android.compose.animation.scene.element
import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
import com.android.compose.animation.scene.nestedScrollToScene
@@ -106,6 +113,26 @@ internal class ContentScopeImpl(
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+ private val _verticalOverscrollEffect =
+ OffsetOverscrollEffect(
+ orientation = Orientation.Vertical,
+ animationScope = layoutImpl.animationScope,
+ )
+
+ private val _horizontalOverscrollEffect =
+ OffsetOverscrollEffect(
+ orientation = Orientation.Horizontal,
+ animationScope = layoutImpl.animationScope,
+ )
+
+ val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
+
+ val horizontalOverscrollGestureEffect = GestureEffect(_horizontalOverscrollEffect)
+
+ override val verticalOverscrollEffect = VisualEffect(_verticalOverscrollEffect)
+
+ override val horizontalOverscrollEffect = VisualEffect(_horizontalOverscrollEffect)
+
override fun Modifier.element(key: ElementKey): Modifier {
return element(layoutImpl, content, key)
}
@@ -175,4 +202,24 @@ internal class ContentScopeImpl(
override fun Modifier.noResizeDuringTransitions(): Modifier {
return noResizeDuringTransitions(layoutState = layoutImpl.state)
}
+
+ @Composable
+ override fun NestedSceneTransitionLayout(
+ state: SceneTransitionLayoutState,
+ modifier: Modifier,
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ ) {
+ SceneTransitionLayoutForTesting(
+ state,
+ modifier,
+ onLayoutImpl = null,
+ builder = builder,
+ sharedElementMap = layoutImpl.elements,
+ ancestorContentKeys =
+ remember(layoutImpl.ancestorContentKeys, contentKey) {
+ layoutImpl.ancestorContentKeys + contentKey
+ },
+ lookaheadScope = layoutImpl.lookaheadScope,
+ )
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt
new file mode 100644
index 000000000000..2233debde277
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.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.compose.animation.scene.effect
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+import com.android.compose.ui.util.SpaceVectorConverter
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * An [OverscrollEffect] that uses an [Animatable] to track and animate overscroll values along a
+ * specific [Orientation].
+ */
+interface ContentOverscrollEffect : OverscrollEffect {
+ /** The current overscroll value. */
+ val overscrollDistance: Float
+}
+
+open class BaseContentOverscrollEffect(
+ orientation: Orientation,
+ private val animationScope: CoroutineScope,
+ private val animationSpec: AnimationSpec<Float>,
+) : ContentOverscrollEffect, SpaceVectorConverter by SpaceVectorConverter(orientation) {
+
+ /** The [Animatable] that holds the current overscroll value. */
+ private val animatable = Animatable(initialValue = 0f, visibilityThreshold = 0.5f)
+
+ override val overscrollDistance: Float
+ get() = animatable.value
+
+ override val isInProgress: Boolean
+ get() = overscrollDistance != 0f
+
+ override fun applyToScroll(
+ delta: Offset,
+ source: NestedScrollSource,
+ performScroll: (Offset) -> Offset,
+ ): Offset {
+ val deltaForAxis = delta.toFloat()
+
+ // If we're currently overscrolled, and the user scrolls in the opposite direction, we need
+ // to "relax" the overscroll by consuming some of the scroll delta to bring it back towards
+ // zero.
+ val currentOffset = animatable.value
+ val sameDirection = deltaForAxis.sign == currentOffset.sign
+ val consumedByPreScroll =
+ if (abs(currentOffset) > 0.5 && !sameDirection) {
+ // The user has scrolled in the opposite direction.
+ val prevOverscrollValue = currentOffset
+ val newOverscrollValue = currentOffset + deltaForAxis
+ if (sign(prevOverscrollValue) != sign(newOverscrollValue)) {
+ // Enough to completely cancel the overscroll. We snap the overscroll value
+ // back to zero and consume the corresponding amount of the scroll delta.
+ animationScope.launch { animatable.snapTo(0f) }
+ -prevOverscrollValue
+ } else {
+ // Not enough to cancel the overscroll. We update the overscroll value
+ // accordingly and consume the entire scroll delta.
+ animationScope.launch { animatable.snapTo(newOverscrollValue) }
+ deltaForAxis
+ }
+ } else {
+ 0f
+ }
+ .toOffset()
+
+ // After handling any overscroll relaxation, we pass the remaining scroll delta to the
+ // standard scrolling logic.
+ val leftForScroll = delta - consumedByPreScroll
+ val consumedByScroll = performScroll(leftForScroll)
+ val overscrollDelta = leftForScroll - consumedByScroll
+
+ // If the user is dragging (not flinging), and there's any remaining scroll delta after the
+ // standard scrolling logic has been applied, we add it to the overscroll.
+ if (abs(overscrollDelta.toFloat()) > 0.5 && source == NestedScrollSource.UserInput) {
+ animationScope.launch { animatable.snapTo(currentOffset + overscrollDelta.toFloat()) }
+ }
+
+ return delta
+ }
+
+ override suspend fun applyToFling(
+ velocity: Velocity,
+ performFling: suspend (Velocity) -> Velocity,
+ ) {
+ // We launch a coroutine to ensure the fling animation starts after any pending [snapTo]
+ // animations have finished.
+ // This guarantees a smooth, sequential execution of animations on the overscroll value.
+ coroutineScope {
+ launch {
+ val consumed = performFling(velocity)
+ val remaining = velocity - consumed
+ animatable.animateTo(0f, animationSpec, remaining.toFloat())
+ }
+ }
+ }
+}
+
+/** An overscroll effect that ensures only a single fling animation is triggered. */
+internal class GestureEffect(private val delegate: ContentOverscrollEffect) :
+ ContentOverscrollEffect by delegate {
+ private var shouldFling = false
+
+ override fun applyToScroll(
+ delta: Offset,
+ source: NestedScrollSource,
+ performScroll: (Offset) -> Offset,
+ ): Offset {
+ shouldFling = true
+ return delegate.applyToScroll(delta, source, performScroll)
+ }
+
+ override suspend fun applyToFling(
+ velocity: Velocity,
+ performFling: suspend (Velocity) -> Velocity,
+ ) {
+ if (!shouldFling) {
+ performFling(velocity)
+ return
+ }
+ shouldFling = false
+ delegate.applyToFling(velocity, performFling)
+ }
+
+ suspend fun ensureApplyToFlingIsCalled() {
+ applyToFling(Velocity.Zero) { Velocity.Zero }
+ }
+}
+
+/**
+ * An overscroll effect that only applies visual effects and does not interfere with the actual
+ * scrolling or flinging behavior.
+ */
+internal class VisualEffect(private val delegate: ContentOverscrollEffect) :
+ ContentOverscrollEffect by delegate {
+ override fun applyToScroll(
+ delta: Offset,
+ source: NestedScrollSource,
+ performScroll: (Offset) -> Offset,
+ ): Offset {
+ return performScroll(delta)
+ }
+
+ override suspend fun applyToFling(
+ velocity: Velocity,
+ performFling: suspend (Velocity) -> Velocity,
+ ) {
+ performFling(velocity)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt
new file mode 100644
index 000000000000..f459c46d3e6f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.compose.animation.scene.effect
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+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.node.DelegatableNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ProgressConverter
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+
+/** An [OverscrollEffect] that offsets the content by the overscroll value. */
+class OffsetOverscrollEffect(
+ orientation: Orientation,
+ animationScope: CoroutineScope,
+ animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
+ private var _node: DelegatableNode = newNode()
+ override val node: DelegatableNode
+ get() = _node
+
+ fun newNode(): DelegatableNode {
+ return object : Modifier.Node(), LayoutModifierNode {
+ override fun onDetach() {
+ super.onDetach()
+ // TODO(b/379086317) Remove this workaround: avoid to reuse the same node.
+ _node = newNode()
+ }
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) {
+ val offsetPx = computeOffset(density = this@measure, overscrollDistance)
+ placeable.placeRelativeWithLayer(position = offsetPx.toIntOffset())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val MaxDistance = 400.dp
+
+ internal val DefaultAnimationSpec =
+ spring(
+ stiffness = Spring.StiffnessLow,
+ dampingRatio = Spring.DampingRatioLowBouncy,
+ visibilityThreshold = 0.5f,
+ )
+
+ @VisibleForTesting
+ internal fun computeOffset(density: Density, overscrollDistance: Float): Int {
+ val maxDistancePx = with(density) { MaxDistance.toPx() }
+ val progress = ProgressConverter.Default.convert(overscrollDistance / maxDistancePx)
+ return (progress * maxDistancePx).roundToInt()
+ }
+ }
+}
+
+@Composable
+fun rememberOffsetOverscrollEffect(
+ orientation: Orientation,
+ animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+): OffsetOverscrollEffect {
+ val animationScope = rememberCoroutineScope()
+ return remember(orientation, animationScope, animationSpec) {
+ OffsetOverscrollEffect(orientation, animationScope, animationSpec)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index a5be4dc195bc..98a00173f1d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -48,8 +48,8 @@ fun LargeTopAppBarNestedScrollConnection(
orientation = Orientation.Vertical,
// When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
// expand. Then, you can then scroll down the content.
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
- offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
+ canStartPreScroll = { offsetAvailable, _, _ ->
+ offsetAvailable < 0 && height() > minHeight()
},
// When swiping down, the content will scroll up until it reaches the top. Then, the
// LargeTopAppBar will expand until it reaches its [maxHeight].
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt
new file mode 100644
index 000000000000..1b5341b8048a
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.ui.util
+
+/**
+ * This is a custom implementation that resembles a SortedMap<Int, T> but is based on a simple
+ * ArrayList to avoid the allocation overhead and boxing.
+ *
+ * It can only hold positive keys and 0 and it is only efficient for small keys (0 - ~100), but
+ * therefore provides fast operations for small keys.
+ */
+internal class IntIndexedMap<T> {
+ private val arrayList = ArrayList<T?>()
+ private var _size = 0
+ val size
+ get() = _size
+
+ /** Returns the value at [key] or null if the key is not present. */
+ operator fun get(key: Int): T? {
+ if (key < 0 || key >= arrayList.size) return null
+ return arrayList[key]
+ }
+
+ /**
+ * Sets the value at [key] to [value]. If [key] is larger than the current size of the map, this
+ * operation may take up to O(key) time and space. Therefore this data structure is only
+ * efficient for small [key] sizes.
+ */
+ operator fun set(key: Int, value: T?) {
+ if (key < 0)
+ throw UnsupportedOperationException("This map can only hold positive keys and 0.")
+ if (key < arrayList.size) {
+ if (arrayList[key] != null && value == null) _size--
+ if (arrayList[key] == null && value != null) _size++
+ arrayList[key] = value
+ } else {
+ if (value == null) return
+ while (key > arrayList.size) {
+ arrayList.add(null)
+ }
+ _size++
+ arrayList.add(value)
+ }
+ }
+
+ /** Remove value at [key] */
+ fun remove(key: Int) {
+ if (key >= arrayList.size) return
+ this[key] = null
+ }
+
+ /** Get the [value] with the smallest [key] of the map. */
+ fun first(): T {
+ for (i in 0 until arrayList.size) {
+ return arrayList[i] ?: continue
+ }
+ throw NoSuchElementException("The map is empty.")
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
index f08a18046537..ca50e778d131 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
@@ -18,6 +18,7 @@ package com.android.compose.ui.util
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.Velocity
interface SpaceVectorConverter {
@@ -25,9 +26,13 @@ interface SpaceVectorConverter {
fun Velocity.toFloat(): Float
+ fun IntOffset.toInt(): Int
+
fun Float.toOffset(): Offset
fun Float.toVelocity(): Velocity
+
+ fun Int.toIntOffset(): IntOffset
}
fun SpaceVectorConverter(orientation: Orientation) =
@@ -36,24 +41,30 @@ fun SpaceVectorConverter(orientation: Orientation) =
Orientation.Vertical -> VerticalConverter
}
-private val HorizontalConverter =
- object : SpaceVectorConverter {
- override fun Offset.toFloat() = x
+private data object HorizontalConverter : SpaceVectorConverter {
+ override fun Offset.toFloat() = x
- override fun Velocity.toFloat() = x
+ override fun Velocity.toFloat() = x
- override fun Float.toOffset() = Offset(this, 0f)
+ override fun IntOffset.toInt() = x
- override fun Float.toVelocity() = Velocity(this, 0f)
- }
+ override fun Float.toOffset() = Offset(this, 0f)
-private val VerticalConverter =
- object : SpaceVectorConverter {
- override fun Offset.toFloat() = y
+ override fun Float.toVelocity() = Velocity(this, 0f)
- override fun Velocity.toFloat() = y
+ override fun Int.toIntOffset() = IntOffset(this, 0)
+}
- override fun Float.toOffset() = Offset(0f, this)
+private data object VerticalConverter : SpaceVectorConverter {
+ override fun Offset.toFloat() = y
- override fun Float.toVelocity() = Velocity(0f, this)
- }
+ override fun Velocity.toFloat() = y
+
+ override fun IntOffset.toInt() = y
+
+ override fun Float.toOffset() = Offset(0f, this)
+
+ override fun Float.toVelocity() = Velocity(0f, this)
+
+ override fun Int.toIntOffset() = IntOffset(0, this)
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
index 174ad30a8f1d..2b76d7ba267e 100644
--- a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -18,7 +18,8 @@
package="com.android.compose.animation.scene.tests" >
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 3644b3069fb3..2fd1d8d8573a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -18,7 +18,10 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -495,4 +498,13 @@ class AnimatedSharedAsStateTest {
assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f)
assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f)
}
+
+ @Test
+ fun interpolatedColor() {
+ val a = Color.Red
+ val b = Color.Green
+ val delta = SharedColorType.diff(b, a) // b - a
+ val interpolated = SharedColorType.addWeighted(a, delta, 0.5f) // a + (b - a) * 0.5f
+ rule.setContent { Box(Modifier.fillMaxSize().background(interpolated)) }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index a1077cf95133..2c8dc3264b7e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -234,12 +234,6 @@ class DraggableHandlerTest {
)
}
- fun onDragStartedImmediately(
- pointersInfo: PointersInfo.PointersDown = pointersDown()
- ): DragController {
- return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
- }
-
fun onDragStarted(
draggableHandler: DraggableHandler,
pointersInfo: PointersInfo.PointersDown = pointersDown(),
@@ -424,37 +418,6 @@ class DraggableHandlerTest {
}
@Test
- fun onDragReversedDirection_changeToScene() = runGestureTest {
- // Drag A -> B with progress 0.6
- val dragController = onDragStarted(overSlop = -60f)
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneB,
- progress = 0.6f,
- )
-
- // Reverse direction such that A -> C now with 0.4
- dragController.onDragDelta(pixels = 100f)
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneC,
- progress = 0.4f,
- )
-
- // After the drag stopped scene C should be committed
- dragController.onDragStoppedAnimateNow(
- velocity = velocityThreshold,
- onAnimationStart = {
- assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
- },
- expectedConsumedVelocity = velocityThreshold,
- )
- assertIdle(currentScene = SceneC)
- }
-
- @Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
onDragStarted(
horizontalDraggableHandler,
@@ -504,31 +467,9 @@ class DraggableHandlerTest {
}
@Test
- fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
- // We are on SceneA. UP -> B, DOWN-> C.
- val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneB,
- progress = 0.2f,
- )
-
- // Reverse drag direction, it will replace the previous transition
- dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneC,
- progress = 0.3f,
- )
- }
-
- @Test
fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
// We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
- mutableUserActionsA =
- mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+ mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB), Swipe.Down to SceneC)
val dragController =
onDragStarted(
pointersInfo =
@@ -542,7 +483,7 @@ class DraggableHandlerTest {
progress = 0.2f,
)
- // Reverse drag direction, it cannot replace the previous transition
+ // Reverse drag direction, it does not replace the previous transition.
dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
assertTransition(
currentScene = SceneA,
@@ -602,82 +543,6 @@ class DraggableHandlerTest {
}
@Test
- fun onAcceleratedScroll_scrollToThirdScene() = runGestureTest {
- // Drag A -> B with progress 0.2
- val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- assertTransition(
- currentScene = SceneA,
- fromScene = SceneA,
- toScene = SceneB,
- progress = 0.2f,
- )
-
- // Start animation A -> B with progress 0.2 -> 1.0
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
- // the transition to B -> C with progress 0.2
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 1f))
- assertTransition(
- currentScene = SceneB,
- fromScene = SceneB,
- toScene = SceneC,
- progress = 0.2f,
- )
-
- // After the drag stopped scene C should be committed
- dragController2.onDragStoppedAnimateNow(
- velocity = -velocityThreshold,
- onAnimationStart = {
- assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
- },
- expectedConsumedVelocity = -velocityThreshold,
- )
- assertIdle(currentScene = SceneC)
- }
-
- @Test
- fun onAcceleratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- mutableUserActionsA = emptyMap()
- mutableUserActionsB = emptyMap()
-
- // start acceleratedScroll and scroll over to B -> null
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-
- // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
- // still be called. Make sure that they don't crash or change the scene
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = {
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- },
- expectedConsumedVelocity = 0f,
- )
-
- advanceUntilIdle()
- assertIdle(SceneB)
-
- // These events can still come in after the animation has settled
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
- dragController2.onDragStoppedAnimateNow(
- velocity = 0f,
- onAnimationStart = { assertIdle(SceneB) },
- expectedConsumedVelocity = 0f,
- )
- assertIdle(SceneB)
- }
-
- @Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
@@ -711,9 +576,8 @@ class DraggableHandlerTest {
dragController1.onDragStoppedAnimateLater(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- val dragController2 = onDragStartedImmediately()
- dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
- assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
}
@Test
@@ -728,7 +592,7 @@ class DraggableHandlerTest {
assertThat(isUserInputOngoing).isFalse()
// Start a new gesture while the offset is animating
- onDragStartedImmediately()
+ onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertThat(isUserInputOngoing).isTrue()
}
@@ -812,36 +676,6 @@ class DraggableHandlerTest {
}
@Test
- fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest {
- val firstScroll = (transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
- val secondScroll = 1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertIdle(SceneA)
- }
-
- @Test
- fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest {
- val firstScroll = (transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
- val secondScroll = 1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertTransition(progress = (firstScroll + secondScroll) / SCREEN_SIZE)
- }
-
- @Test
- fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest {
- val firstScroll = -(1f - transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
- val secondScroll = -1f
-
- preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
- assertTransition(progress = -(firstScroll + secondScroll) / SCREEN_SIZE)
- }
-
- @Test
fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest {
val firstScroll = -(1f - transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
val secondScroll = -0.01f
@@ -1025,7 +859,7 @@ class DraggableHandlerTest {
// now we can intercept the scroll events
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.2f)
+ assertThat(progress).isEqualTo(0.1f)
// this should be ignored, we are scrolling now!
dragController.onDragStoppedAnimateNow(
@@ -1036,10 +870,10 @@ class DraggableHandlerTest {
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.3f)
+ assertThat(progress).isEqualTo(0.2f)
nestedScroll.scroll(available = -offsetY10)
- assertThat(progress).isEqualTo(0.4f)
+ assertThat(progress).isEqualTo(0.3f)
nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
assertTransition(currentScene = SceneB)
@@ -1050,57 +884,6 @@ class DraggableHandlerTest {
}
@Test
- fun interceptTransition() = runGestureTest {
- // Start at scene C.
- navigateToSceneC()
-
- // Swipe up from the middle to transition to scene B.
- val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.1f,
- isUserInputOngoing = true,
- )
-
- val firstTransition = transitionState
-
- // During the current gesture, start a new gesture, still in the middle of the screen. We
- // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
- // should be 0f.
- assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
- onDragStartedImmediately(pointersInfo = middle)
-
- // We should have intercepted the transition, so the transition should be the same object.
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneB,
- progress = 0.1f,
- isUserInputOngoing = true,
- )
- // We should have a new transition
- assertThat(transitionState).isNotSameInstanceAs(firstTransition)
-
- // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
- // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
- // instead animate from C to A.
- val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
- assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
-
- assertTransition(
- currentScene = SceneC,
- fromScene = SceneC,
- toScene = SceneA,
- isUserInputOngoing = true,
- )
- assertThat(transitionState).isNotSameInstanceAs(firstTransition)
- }
-
- @Test
fun freezeAndAnimateToCurrentState() = runGestureTest {
// Start at scene C.
navigateToSceneC()
@@ -1110,9 +893,6 @@ class DraggableHandlerTest {
onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
- // The current transition can be intercepted.
- assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
-
// Freeze the transition.
val transition = transitionState as Transition
transition.freezeAndAnimateToCurrentState()
@@ -1123,19 +903,6 @@ class DraggableHandlerTest {
}
@Test
- fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
- onDragStarted(overSlop = up(0.1f))
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
-
- layoutState.startTransitionImmediately(
- animationScope = testScope.backgroundScope,
- transition(SceneA, SceneB),
- )
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
- }
-
- @Test
fun blockTransition() = runGestureTest {
assertIdle(SceneA)
@@ -1154,30 +921,6 @@ class DraggableHandlerTest {
}
@Test
- fun blockInterceptedTransition() = runGestureTest {
- assertIdle(SceneA)
-
- // Swipe up to B.
- val dragController1 = onDragStarted(overSlop = up(0.1f))
- assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
- dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
- assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
- // Intercept the transition and swipe down back to scene A.
- assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
- val dragController2 = onDragStartedImmediately()
-
- // Block the transition when the user release their finger.
- canChangeScene = { false }
- dragController2.onDragStoppedAnimateNow(
- velocity = velocityThreshold,
- onAnimationStart = { assertTransition(fromScene = SceneA, toScene = SceneB) },
- expectedConsumedVelocity = velocityThreshold,
- )
- assertIdle(SceneB)
- }
-
- @Test
fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
layoutState.transitions = transitions {
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
@@ -1531,25 +1274,6 @@ class DraggableHandlerTest {
}
@Test
- fun interceptingTransitionKeepsDistance() = runGestureTest {
- var swipeDistance = 75f
- layoutState.transitions = transitions {
- from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } }
- }
-
- // Start transition.
- val controller = onDragStarted(overSlop = -50f)
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
-
- // Intercept the transition and change the swipe distance. The original distance and
- // progress should be the same.
- swipeDistance = 50f
- controller.onDragStoppedAnimateLater(0f)
- onDragStartedImmediately()
- assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
- }
-
- @Test
fun requireFullDistanceSwipe() = runGestureTest {
mutableUserActionsA +=
Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
@@ -1579,19 +1303,6 @@ class DraggableHandlerTest {
}
@Test
- fun interceptingTransitionReplacesCurrentTransition() = runGestureTest {
- val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f))
- val transition = assertThat(layoutState.transitionState).isSceneTransition()
- controller.onDragStoppedAnimateLater(velocity = 0f)
-
- // Intercept the transition.
- onDragStartedImmediately()
- val newTransition = assertThat(layoutState.transitionState).isSceneTransition()
- assertThat(newTransition).isNotSameInstanceAs(transition)
- assertThat(newTransition.replacedTransition).isSameInstanceAs(transition)
- }
-
- @Test
fun showOverlay() = runGestureTest {
mutableUserActionsA = mapOf(Swipe.Down to UserActionResult.ShowOverlay(OverlayA))
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index a301856d024f..f1da01fef72c 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.overscroll
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.Text
@@ -47,6 +48,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
@@ -60,6 +62,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
@@ -72,6 +75,7 @@ import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.effect.OffsetOverscrollEffect
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.setContentAndCreateMainScope
@@ -712,7 +716,7 @@ class ElementTest {
}
@Test
- fun elementTransitionDuringOverscroll() {
+ fun elementTransitionDuringOverscrollWithOverscrollDSL() {
val layoutWidth = 200.dp
val layoutHeight = 400.dp
val overscrollTranslateY = 10.dp
@@ -765,6 +769,241 @@ class ElementTest {
assertThat(animatedFloat).isEqualTo(100f)
}
+ private fun expectedOffset(currentOffset: Dp, density: Density): Dp {
+ return with(density) {
+ OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp()
+ }
+ }
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ lateinit var density: Density
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) },
+ )
+ }
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(SceneB) {
+ Spacer(
+ Modifier.overscroll(verticalOverscrollEffect)
+ .fillMaxSize()
+ .element(TestElements.Foo)
+ )
+ }
+ }
+ }
+ assertThat(state.transitionState).isIdle()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+ down(middleTop)
+ // Scroll 50%.
+ val firstScrollHeight = layoutHeight.toPx() * 0.5f
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
+ }
+
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).isNotNull()
+ assertThat(transition).hasProgress(0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%.
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 150% (Scene B overscroll by 50%).
+ assertThat(transition).hasProgress(1f)
+
+ rule
+ .onNodeWithTag(TestElements.Foo.testTag)
+ .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density))
+ }
+
+ @Test
+ fun elementTransitionOverscrollMultipleScenes() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ lateinit var density: Density
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ overscrollDisabled(SceneA, Orientation.Vertical)
+ overscrollDisabled(SceneB, Orientation.Vertical)
+ },
+ )
+ }
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(
+ Modifier.overscroll(verticalOverscrollEffect)
+ .fillMaxSize()
+ .element(TestElements.Foo)
+ )
+ }
+ scene(SceneB) {
+ Spacer(
+ Modifier.overscroll(verticalOverscrollEffect)
+ .fillMaxSize()
+ .element(TestElements.Bar)
+ )
+ }
+ }
+ }
+ assertThat(state.transitionState).isIdle()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+ down(middleTop)
+ val firstScrollHeight = layoutHeight.toPx() * 0.5f // Scroll 50%
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
+ }
+
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).isNotNull()
+ assertThat(transition).hasProgress(0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%.
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 150% (Scene B overscroll by 50%).
+ assertThat(transition).hasProgress(1f)
+
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ rule
+ .onNodeWithTag(TestElements.Bar.testTag)
+ .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density))
+
+ rule.onRoot().performTouchInput {
+ // Scroll another -30%.
+ moveBy(Offset(0f, layoutHeight.toPx() * -0.3f), delayMillis = 1_000)
+ }
+
+ // Scroll 120% (Scene B overscroll by 20%).
+ assertThat(transition).hasProgress(1f)
+
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ rule
+ .onNodeWithTag(TestElements.Bar.testTag)
+ .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.2f, density))
+ rule.onRoot().performTouchInput {
+ // Scroll another -70%
+ moveBy(Offset(0f, layoutHeight.toPx() * -0.7f), delayMillis = 1_000)
+ }
+
+ // Scroll 50% (No overscroll).
+ assertThat(transition).hasProgress(0.5f)
+
+ rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another -100%.
+ moveBy(Offset(0f, layoutHeight.toPx() * -1f), delayMillis = 1_000)
+ }
+
+ // Scroll -50% (Scene A overscroll by -50%).
+ assertThat(transition).hasProgress(0f)
+ rule
+ .onNodeWithTag(TestElements.Foo.testTag)
+ .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * -0.5f, density))
+ rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun elementTransitionOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ lateinit var density: Density
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ defaultOverscrollProgressConverter = ProgressConverter.linear()
+ overscrollDisabled(SceneB, Orientation.Vertical)
+ },
+ )
+ }
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(SceneB) {
+ Spacer(
+ Modifier.overscroll(verticalOverscrollEffect)
+ .element(TestElements.Foo)
+ .fillMaxSize()
+ )
+ }
+ }
+ }
+ assertThat(state.transitionState).isIdle()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+ down(middleTop)
+ val firstScrollHeight = layoutHeight.toPx() * 0.5f // Scroll 50%
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
+ }
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).isNotNull()
+ assertThat(transition).hasProgress(0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%.
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 150% (Scene B overscroll by 50%).
+ assertThat(transition).hasProgress(1f)
+
+ fooElement.assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density))
+ }
+
@Test
fun elementTransitionDuringNestedScrollOverscroll() {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index cb3e433ec4a5..4153350fce60 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -101,7 +101,6 @@ class MultiPointerDraggableTest {
.thenIf(enabled) {
Modifier.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -169,8 +168,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- // We want to start a drag gesture immediately
- startDragImmediately = { true },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -242,7 +239,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -361,7 +357,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
started = true
SimpleDragController(
@@ -466,7 +461,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
verticalStarted = true
SimpleDragController(
@@ -478,7 +472,6 @@ class MultiPointerDraggableTest {
)
.multiPointerDraggable(
orientation = Orientation.Horizontal,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
horizontalStarted = true
SimpleDragController(
@@ -570,7 +563,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
swipeDetector =
object : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -636,7 +628,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
swipeDetector =
object : SwipeDetector {
override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -738,7 +729,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { consumedOnDrag = it },
@@ -809,7 +799,6 @@ class MultiPointerDraggableTest {
.nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
- startDragImmediately = { false },
onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { /* do nothing */ },
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 79ca891babd1..3b7d661ba91a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,12 +18,9 @@ package com.android.compose.animation.scene
import android.util.Log
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.TestOverlays.OverlayA
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -169,130 +166,6 @@ class SceneTransitionLayoutStateTest {
assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
- @Test
- fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
- }
-
- @Test
- fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-
- val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
- state.startTransitionImmediately(animationScope = backgroundScope, aToB)
- assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
-
- val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
- state.startTransitionImmediately(animationScope = backgroundScope, bToC)
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- assertThat(state.currentTransitions).isEmpty()
- }
-
- @Test
- fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- var progress by mutableStateOf(0f)
- var currentScene by mutableStateOf(SceneB)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(
- from = SceneA,
- to = SceneB,
- current = { currentScene },
- progress = { progress },
- ),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if we are close to a scene that is not the current scene
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- progress = 1f
- currentScene = SceneA
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
- }
-
private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
progress: () -> Float,
sceneTransitions: SceneTransitions,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index b3a3261122a8..fe7b5b6bf4da 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -664,17 +664,11 @@ class SwipeToSceneTest {
}
}
- // Swipe down for the default transition from A to B.
+ // Move the pointer up to swipe to scene B using the new transition.
rule.onRoot().performTouchInput {
- down(middle)
- moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+ down(center)
+ moveBy(Offset(0f, -touchSlop - 1.dp.toPx()), delayMillis = 1_000)
}
-
- assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
- assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1)
-
- // Move the pointer up to swipe to scene B using the new transition.
- rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) }
assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt
new file mode 100644
index 000000000000..d267cc5c237f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.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.compose.animation.scene.effect
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.overscroll
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OffsetOverscrollEffectTest {
+ @get:Rule val rule = createComposeRule()
+
+ private fun expectedOffset(currentOffset: Dp, density: Density): Dp {
+ return with(density) {
+ OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp()
+ }
+ }
+
+ @Test
+ fun applyVerticalOffset_duringVerticalOverscroll() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+ }
+
+ @Test
+ fun applyNoOffset_duringHorizontalOverscroll() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Horizontal,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(touchSlop + layoutSize.toPx(), 0f), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun backToZero_afterOverscroll() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+
+ rule.onRoot().performTouchInput { up() }
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun offsetOverscroll_followTheTouchPointer() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ // A full screen scroll.
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+
+ rule.onRoot().performTouchInput {
+ // Reduced by half.
+ moveBy(Offset(0f, -layoutSize.toPx() / 2), delayMillis = 1_000)
+ }
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize / 2, density))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
new file mode 100644
index 000000000000..c6ef8cff1a66
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+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.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.AutoTransitionTestAssertionScope
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.Default4FrameLinearTransition
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.inScene
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.animation.scene.transitions
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NestedSharedElementTest {
+ @get:Rule val rule = createComposeRule()
+
+ private object Scenes {
+ val NestedSceneA = SceneKey("NestedSceneA")
+ val NestedSceneB = SceneKey("NestedSceneB")
+ val NestedNestedSceneA = SceneKey("NestedNestedSceneA")
+ val NestedNestedSceneB = SceneKey("NestedNestedSceneB")
+ }
+
+ private val elementVariant1 = SharedElement(0.dp, 0.dp, 100.dp, 100.dp, Color.Red)
+ private val elementVariant2 = SharedElement(40.dp, 80.dp, 60.dp, 20.dp, Color.Blue)
+ private val elementVariant3 = SharedElement(80.dp, 40.dp, 140.dp, 180.dp, Color.Yellow)
+ private val elementVariant4 = SharedElement(120.dp, 240.dp, 20.dp, 140.dp, Color.Green)
+
+ private class SharedElement(
+ val x: Dp,
+ val y: Dp,
+ val width: Dp,
+ val height: Dp,
+ val color: Color = Color.Black,
+ val alpha: Float = 0.8f,
+ )
+
+ @Composable
+ private fun ContentScope.SharedElement(element: SharedElement) {
+ Box(Modifier.fillMaxSize()) {
+ Box(
+ Modifier.offset(element.x, element.y)
+ .element(TestElements.Foo)
+ .size(element.width, element.height)
+ .background(element.color)
+ .alpha(element.alpha)
+ )
+ }
+ }
+
+ private val contentWithSharedElement: @Composable ContentScope.() -> Unit = {
+ SharedElement(elementVariant1)
+ }
+
+ private val nestedState: MutableSceneTransitionLayoutState =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ Scenes.NestedSceneA,
+ transitions {
+ from(
+ from = Scenes.NestedSceneA,
+ to = Scenes.NestedSceneB,
+ builder = Default4FrameLinearTransition,
+ )
+ },
+ )
+ }
+
+ private val nestedNestedState: MutableSceneTransitionLayoutState =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ Scenes.NestedNestedSceneA,
+ transitions {
+ from(
+ from = Scenes.NestedNestedSceneA,
+ to = Scenes.NestedNestedSceneB,
+ builder = Default4FrameLinearTransition,
+ )
+ },
+ )
+ }
+
+ private val nestedStlWithSharedElement: @Composable ContentScope.() -> Unit = {
+ NestedSceneTransitionLayout(nestedState, modifier = Modifier) {
+ scene(Scenes.NestedSceneA) { SharedElement(elementVariant2) }
+ scene(Scenes.NestedSceneB) { SharedElement(elementVariant3) }
+ }
+ }
+
+ private val nestedNestedStlWithSharedElement: @Composable ContentScope.() -> Unit = {
+ NestedSceneTransitionLayout(nestedState, modifier = Modifier) {
+ scene(Scenes.NestedSceneA) {
+ NestedSceneTransitionLayout(state = nestedNestedState, modifier = Modifier) {
+ scene(Scenes.NestedNestedSceneA) { SharedElement(elementVariant4) }
+ scene(Scenes.NestedNestedSceneB) { SharedElement(elementVariant3) }
+ }
+ }
+ scene(Scenes.NestedSceneB) { SharedElement(elementVariant2) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElementTransition_fromNestedSTLtoParentSTL() {
+ rule.testTransition(
+ fromSceneContent = nestedStlWithSharedElement,
+ toSceneContent = contentWithSharedElement,
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertBetweenElementVariants(elementVariant2, elementVariant1, this)
+ }
+ after { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElementTransition_fromParentSTLtoNestedSTL() {
+ rule.testTransition(
+ fromSceneContent = contentWithSharedElement,
+ toSceneContent = nestedStlWithSharedElement,
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneA)
+ .assertBetweenElementVariants(elementVariant1, elementVariant2, this)
+ }
+ after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElementTransition_fromParentSTLtoNestedNestedSTL() {
+ rule.testTransition(
+ fromSceneContent = contentWithSharedElement,
+ toSceneContent = nestedNestedStlWithSharedElement,
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneA)
+ .assertBetweenElementVariants(elementVariant1, elementVariant4, this)
+ }
+ after { onElement(TestElements.Foo).assertElementVariant(elementVariant4) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElementTransition_fromNestedNestedSTLtoNestedSTL() {
+ rule.testTransition(
+ fromSceneContent = nestedNestedStlWithSharedElement,
+ toSceneContent = { Box(modifier = Modifier.fillMaxSize()) },
+ changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, this) },
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant4) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+ onElement(TestElements.Foo, Scenes.NestedNestedSceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, Scenes.NestedSceneB)
+ .assertBetweenElementVariants(elementVariant4, elementVariant2, this)
+ }
+ after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElement_sharedElementTransitionIsDisabled() {
+ rule.testTransition(
+ fromSceneContent = contentWithSharedElement,
+ toSceneContent = nestedStlWithSharedElement,
+ transition = {
+ spec = tween(16 * 4, easing = LinearEasing)
+
+ // Disable the shared element animation.
+ sharedElement(TestElements.Foo, enabled = false)
+
+ // In SceneA, Foo leaves to the left edge.
+ translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left, false)
+
+ // We can't reference the element inside the NestedSTL as of today
+ },
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, scene = TestScenes.SceneA)
+ .assertPositionInRootIsEqualTo(
+ interpolate(elementVariant1.x, 0.dp),
+ elementVariant1.y,
+ )
+ .assertSizeIsEqualTo(elementVariant1.width, elementVariant1.height)
+ }
+ after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+ }
+ }
+
+ @Test
+ fun nestedSharedElementTransition_transitionInsideNestedStl() {
+ rule.testTransition(
+ layoutModifier = Modifier.fillMaxSize(),
+ fromSceneContent = nestedStlWithSharedElement,
+ toSceneContent = contentWithSharedElement,
+ changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, animationScope = this) },
+ ) {
+ before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+ atAllFrames(4) {
+ onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, scene = Scenes.NestedSceneB)
+ .assertBetweenElementVariants(elementVariant2, elementVariant3, this)
+ }
+ after {
+ onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+ onElement(TestElements.Foo).assertElementVariant(elementVariant3)
+ }
+ }
+ }
+
+ private fun SemanticsNodeInteraction.assertElementVariant(variant: SharedElement) {
+ assertPositionInRootIsEqualTo(variant.x, variant.y)
+ assertSizeIsEqualTo(variant.width, variant.height)
+ }
+
+ private fun SemanticsNodeInteraction.assertBetweenElementVariants(
+ from: SharedElement,
+ to: SharedElement,
+ assertScope: AutoTransitionTestAssertionScope,
+ ) {
+ assertPositionInRootIsEqualTo(
+ assertScope.interpolate(from.x, to.x),
+ assertScope.interpolate(from.y, to.y),
+ )
+ assertSizeIsEqualTo(
+ assertScope.interpolate(from.width, to.width),
+ assertScope.interpolate(from.height, to.height),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2e3a934c2701..47c10f5ab3a3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -62,35 +62,14 @@ class SharedElementTest {
onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp)
onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
}
- at(0) {
- // Shared elements are by default placed and drawn only in the scene with highest
- // zIndex.
+ atAllFrames(4) {
onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
- onElement(TestElements.Foo, TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(10.dp, 50.dp)
- .assertSizeIsEqualTo(20.dp, 80.dp)
- }
- at(16) {
- onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
- onElement(TestElements.Foo, TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(20.dp, 55.dp)
- .assertSizeIsEqualTo(17.5.dp, 70.dp)
- }
- at(32) {
- onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
- onElement(TestElements.Foo, TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(30.dp, 60.dp)
- .assertSizeIsEqualTo(15.dp, 60.dp)
- }
- at(48) {
- onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
onElement(TestElements.Foo, TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(40.dp, 65.dp)
- .assertSizeIsEqualTo(12.5.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(
+ interpolate(10.dp, 50.dp),
+ interpolate(50.dp, 70.dp),
+ )
+ .assertSizeIsEqualTo(interpolate(20.dp, 10.dp), interpolate(80.dp, 40.dp))
}
after {
onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
@@ -132,29 +111,11 @@ class SharedElementTest {
},
) {
before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
- at(0) {
- onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(10.dp, 50.dp)
- onElement(TestElements.Foo, scene = TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(50.dp, 100.dp)
- }
- at(16) {
- onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(7.5.dp, 50.dp)
- onElement(TestElements.Foo, scene = TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(50.dp, 90.dp)
- }
- at(32) {
- onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(5.dp, 50.dp)
- onElement(TestElements.Foo, scene = TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(50.dp, 80.dp)
- }
- at(48) {
+ atAllFrames(4) {
onElement(TestElements.Foo, scene = TestScenes.SceneA)
- .assertPositionInRootIsEqualTo(2.5.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(interpolate(10.dp, 0.dp), 50.dp)
onElement(TestElements.Foo, scene = TestScenes.SceneB)
- .assertPositionInRootIsEqualTo(50.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(50.dp, interpolate(100.dp, 60.dp))
}
after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index e27f9b52153d..c8fb2cb8474f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -71,41 +71,6 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
}
@Test
- fun onScrollUpAfterContentScrolled_ignoreUpEvent() {
- val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
- height = 1f
-
- // scroll down consumed by a child
- scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f))
-
- val offsetConsumed =
- scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
-
- // It should ignore all onPreScroll events
- assertThat(offsetConsumed).isEqualTo(Offset.Zero)
- assertThat(height).isEqualTo(1f)
- }
-
- @Test
- fun onScrollUpAfterContentReturnedToZero_consumeHeight() {
- val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
- height = 1f
-
- // scroll down consumed by a child
- scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f))
-
- // scroll up consumed by a child, the child is in its original position
- scrollConnection.scroll(available = Offset(0f, -1f), consumedByScroll = Offset(0f, -1f))
-
- val offsetConsumed =
- scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
-
- // It should ignore all onPreScroll events
- assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f))
- assertThat(height).isEqualTo(0f)
- }
-
- @Test
fun onScrollUp_consumeDownToMin() {
val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
height = 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt
new file mode 100644
index 000000000000..d7a9b9007be0
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.compose.ui.util
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class IntIndexMapTest {
+
+ @Test
+ fun testSetGetFirstAndSize() {
+ val map = IntIndexedMap<String>()
+
+ // Write first element at 10
+ map[10] = "1"
+ assertThat(map[10]).isEqualTo("1")
+ assertThat(map.size).isEqualTo(1)
+ assertThat(map.first()).isEqualTo("1")
+
+ // Write same element to same index
+ map[10] = "1"
+ assertThat(map[10]).isEqualTo("1")
+ assertThat(map.size).isEqualTo(1)
+
+ // Writing into larger index
+ map[12] = "2"
+ assertThat(map[12]).isEqualTo("2")
+ assertThat(map.size).isEqualTo(2)
+ assertThat(map.first()).isEqualTo("1")
+
+ // Overwriting existing index
+ map[10] = "3"
+ assertThat(map[10]).isEqualTo("3")
+ assertThat(map.size).isEqualTo(2)
+ assertThat(map.first()).isEqualTo("3")
+
+ // Writing into smaller index
+ map[0] = "4"
+ assertThat(map[0]).isEqualTo("4")
+ assert(map.size == 3)
+ assertThat(map.first()).isEqualTo("4")
+
+ // Writing null into non-null index
+ map[0] = null
+ assertThat(map[0]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(2)
+ assertThat(map.first()).isEqualTo("3")
+
+ // Writing null into smaller null index
+ map[1] = null
+ assertThat(map[1]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(2)
+
+ // Writing null into larger null index
+ map[15] = null
+ assertThat(map[15]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(2)
+
+ // Remove existing element
+ map.remove(12)
+ assertThat(map[12]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(1)
+
+ // Remove non-existing element
+ map.remove(17)
+ assertThat(map[17]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(1)
+
+ // Remove all elements
+ assertThat(map.first()).isEqualTo("3")
+ map.remove(10)
+ map.remove(10)
+ map.remove(0)
+ assertThat(map.size).isEqualTo(0)
+ assertThat(map[10]).isEqualTo(null)
+ assertThat(map.size).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 0d2fcfc0b790..124b61e45ed6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -27,6 +29,9 @@ import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion
@@ -62,6 +67,16 @@ interface TransitionTestBuilder {
fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit)
/**
+ * Run the same assertion for all frames of a transition.
+ *
+ * @param totalFrames needs to be the exact number of frames of the transition that is run,
+ * otherwise the passed progress will be incorrect. That is the duration in ms divided by 16.
+ * @param builder is passed a progress Float which can be used to calculate values for the
+ * specific frame. Or use [AutoTransitionTestAssertionScope.interpolate].
+ */
+ fun atAllFrames(totalFrames: Int, builder: AutoTransitionTestAssertionScope.(Float) -> Unit)
+
+ /**
* Assert on the state of the layout after the transition finished.
*
* This should be called maximum once, after [before] or [at] is called.
@@ -82,6 +97,16 @@ interface TransitionTestAssertionScope {
fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
}
+interface AutoTransitionTestAssertionScope : TransitionTestAssertionScope {
+
+ /** Linear interpolate [from] and [to] with the current progress of the transition. */
+ fun <T> interpolate(from: T, to: T): T
+}
+
+val Default4FrameLinearTransition: TransitionBuilder.() -> Unit = {
+ spec = tween(16 * 4, easing = LinearEasing)
+}
+
/**
* Test the transition between [fromSceneContent] and [toSceneContent] at different points in time.
*
@@ -90,10 +115,13 @@ interface TransitionTestAssertionScope {
fun ComposeContentTestRule.testTransition(
fromSceneContent: @Composable ContentScope.() -> Unit,
toSceneContent: @Composable ContentScope.() -> Unit,
- transition: TransitionBuilder.() -> Unit,
+ transition: TransitionBuilder.() -> Unit = Default4FrameLinearTransition,
layoutModifier: Modifier = Modifier,
fromScene: SceneKey = TestScenes.SceneA,
toScene: SceneKey = TestScenes.SceneB,
+ changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit = { state ->
+ state.setTargetScene(toScene, animationScope = this)
+ },
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
@@ -104,7 +132,7 @@ fun ComposeContentTestRule.testTransition(
transitions { from(fromScene, to = toScene, builder = transition) },
)
},
- to = toScene,
+ changeState = changeState,
transitionLayout = { state ->
SceneTransitionLayout(state, layoutModifier) {
scene(fromScene, content = fromSceneContent)
@@ -293,13 +321,30 @@ fun ComposeContentTestRule.testTransition(
) {
val test = transitionTest(builder)
val assertionScope =
- object : TransitionTestAssertionScope {
+ object : AutoTransitionTestAssertionScope {
+ var progress = 0f
+
override fun onElement(
element: ElementKey,
scene: SceneKey?,
): SemanticsNodeInteraction {
return onNode(isElement(element, scene))
}
+
+ override fun <T> interpolate(from: T, to: T): T {
+ @Suppress("UNCHECKED_CAST")
+ return when {
+ from is Float && to is Float -> lerp(from, to, progress)
+ from is Int && to is Int -> lerp(from, to, progress)
+ from is Long && to is Long -> lerp(from, to, progress)
+ from is Dp && to is Dp -> lerp(from, to, progress)
+ else ->
+ throw UnsupportedOperationException(
+ "Interpolation not supported for this type"
+ )
+ }
+ as T
+ }
}
lateinit var coroutineScope: CoroutineScope
@@ -321,14 +366,28 @@ fun ComposeContentTestRule.testTransition(
mainClock.advanceTimeByFrame()
waitForIdle()
+ var currentTime = 0L
// Test the assertions at specific points in time.
test.timestamps.forEach { tsAssertion ->
if (tsAssertion.timestampDelta > 0L) {
mainClock.advanceTimeBy(tsAssertion.timestampDelta)
waitForIdle()
+ currentTime += tsAssertion.timestampDelta.toInt()
}
- tsAssertion.assertion(assertionScope)
+ assertionScope.progress = tsAssertion.progress
+ try {
+ tsAssertion.assertion(assertionScope, tsAssertion.progress)
+ } catch (assertionError: AssertionError) {
+ if (assertionScope.progress > 0) {
+ throw AssertionError(
+ "Transition assertion failed at ${currentTime}ms " +
+ "at progress: ${assertionScope.progress}f",
+ assertionError,
+ )
+ }
+ throw assertionError
+ }
}
// Go to the end state and test it.
@@ -371,7 +430,25 @@ private fun transitionTest(builder: TransitionTestBuilder.() -> Unit): Transitio
val delta = timestamp - currentTimestamp
currentTimestamp = timestamp
- timestamps.add(TimestampAssertion(delta, builder))
+ timestamps.add(TimestampAssertion(delta, { builder() }, 0f))
+ }
+
+ override fun atAllFrames(
+ totalFrames: Int,
+ builder: AutoTransitionTestAssertionScope.(Float) -> Unit,
+ ) {
+ check(after == null) { "atFrames(...) {} must be called before after {}" }
+ check(currentTimestamp == 0L) {
+ "atFrames(...) can't be called multiple times or after at(...)"
+ }
+
+ for (frame in 0 until totalFrames) {
+ val timestamp = frame * 16L
+ val delta = timestamp - currentTimestamp
+ val progress = frame.toFloat() / totalFrames
+ currentTimestamp = timestamp
+ timestamps.add(TimestampAssertion(delta, builder, progress))
+ }
}
override fun after(builder: TransitionTestAssertionScope.() -> Unit) {
@@ -396,5 +473,6 @@ private class TransitionTest(
private class TimestampAssertion(
val timestampDelta: Long,
- val assertion: TransitionTestAssertionScope.() -> Unit,
+ val assertion: AutoTransitionTestAssertionScope.(Float) -> Unit,
+ val progress: Float,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
index bcf055bd2c40..15373d354ef6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -33,7 +33,7 @@ data class ClockDesign(
val thumbnail: String? = null,
val large: ClockFace? = null,
val small: ClockFace? = null,
- val colorPalette: MonetStyle? = null,
+ @MonetStyle.Type val colorPalette: Int? = null,
)
/** Describes a clock using layers */
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 300a3e204582..ad9eba841c86 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
@@ -106,10 +106,8 @@ class DefaultClockController(
largeClock.animations = LargeClockAnimations(largeClock.view, dozeFraction, foldFraction)
smallClock.animations = DefaultClockAnimations(smallClock.view, dozeFraction, foldFraction)
- val theme = ThemeConfig(isDarkTheme, settings?.seedColor)
- largeClock.events.onThemeChanged(theme)
- smallClock.events.onThemeChanged(theme)
-
+ largeClock.events.onThemeChanged(largeClock.theme.copy(isDarkTheme = isDarkTheme))
+ smallClock.events.onThemeChanged(smallClock.theme.copy(isDarkTheme = isDarkTheme))
events.onTimeZoneChanged(TimeZone.getDefault())
smallClock.events.onTimeTick()
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 c7a3f63e92e7..7f01fd7c87ac 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
@@ -24,7 +24,6 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
-import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.shared.clocks.view.FlexClockView
@@ -107,18 +106,16 @@ class FlexClockController(
}
override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
- val theme = ThemeConfig(isDarkTheme, clockCtx.settings.seedColor)
events.onFontAxesChanged(clockCtx.settings.axes)
-
smallClock.run {
- events.onThemeChanged(theme)
+ events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
events.onTimeTick()
}
largeClock.run {
- events.onThemeChanged(theme)
+ events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
events.onTimeTick()
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 a8890e6aa934..21d41ae744a7 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
@@ -131,6 +131,7 @@ class FlexClockFaceController(
}
override fun onThemeChanged(theme: ThemeConfig) {
+ this@FlexClockFaceController.theme = theme
layerController.faceEvents.onThemeChanged(theme)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index 34c4dfb4bc54..48af2d9f5542 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -25,6 +25,7 @@ import android.database.ContentObserver
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
+import android.os.Bundle
import android.util.Log
import androidx.annotation.DrawableRes
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
@@ -51,10 +52,7 @@ interface CustomizationProviderClient {
* selected affordances on the slot will move the selected affordance to the newest location in
* the slot.
*/
- suspend fun insertSelection(
- slotId: String,
- affordanceId: String,
- )
+ suspend fun insertSelection(slotId: String, affordanceId: String)
/** Returns all available slots supported by the device. */
suspend fun querySlots(): List<Slot>
@@ -63,6 +61,11 @@ interface CustomizationProviderClient {
suspend fun queryFlags(): List<Flag>
/**
+ * Returns [Bundle] where the keys are from [CustomizationProviderContract.RuntimeValuesTable]
+ */
+ suspend fun queryRuntimeValues(): Bundle
+
+ /**
* Returns [Flow] for observing the collection of slots.
*
* @see [querySlots]
@@ -77,6 +80,13 @@ interface CustomizationProviderClient {
fun observeFlags(): Flow<List<Flag>>
/**
+ * Returns [Flow] for observing the variables from the System UI.
+ *
+ * @see [queryRuntimeValues]
+ */
+ fun observeRuntimeValues(): Flow<Bundle>
+
+ /**
* Returns all available affordances supported by the device, regardless of current slot
* placement.
*/
@@ -100,15 +110,10 @@ interface CustomizationProviderClient {
fun observeSelections(): Flow<List<Selection>>
/** Unselects an affordance with the given ID from the slot with the given ID. */
- suspend fun deleteSelection(
- slotId: String,
- affordanceId: String,
- )
+ suspend fun deleteSelection(slotId: String, affordanceId: String)
/** Unselects all affordances from the slot with the given ID. */
- suspend fun deleteAllSelections(
- slotId: String,
- )
+ suspend fun deleteAllSelections(slotId: String)
/** Returns a [Drawable] with the given ID, loaded from the system UI package. */
suspend fun getAffordanceIcon(
@@ -200,10 +205,7 @@ class CustomizationProviderClientImpl(
private val backgroundDispatcher: CoroutineDispatcher,
) : CustomizationProviderClient {
- override suspend fun insertSelection(
- slotId: String,
- affordanceId: String,
- ) {
+ override suspend fun insertSelection(slotId: String, affordanceId: String) {
withContext(backgroundDispatcher) {
context.contentResolver.insert(
Contract.LockScreenQuickAffordances.SelectionTable.URI,
@@ -211,9 +213,9 @@ class CustomizationProviderClientImpl(
put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId)
put(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
- affordanceId
+ affordanceId,
)
- }
+ },
)
}
}
@@ -221,13 +223,7 @@ class CustomizationProviderClientImpl(
override suspend fun querySlots(): List<CustomizationProviderClient.Slot> {
return withContext(backgroundDispatcher) {
context.contentResolver
- .query(
- Contract.LockScreenQuickAffordances.SlotTable.URI,
- null,
- null,
- null,
- null,
- )
+ .query(Contract.LockScreenQuickAffordances.SlotTable.URI, null, null, null, null)
?.use { cursor ->
buildList {
val idColumnIndex =
@@ -252,42 +248,55 @@ class CustomizationProviderClientImpl(
}
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> {
return withContext(backgroundDispatcher) {
- context.contentResolver
- .query(
- Contract.FlagsTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
+ context.contentResolver.query(Contract.FlagsTable.URI, null, null, null, null)?.use {
+ cursor ->
+ buildList {
+ val nameColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+ val valueColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+ if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ CustomizationProviderClient.Flag(
+ name = cursor.getString(nameColumnIndex),
+ value = cursor.getInt(valueColumnIndex) == 1,
+ )
+ )
+ }
+ }
+ }
+ } ?: emptyList()
+ }
+
+ override suspend fun queryRuntimeValues(): Bundle {
+ return withContext(backgroundDispatcher) {
+ Bundle().apply {
+ context.contentResolver
+ .query(Contract.RuntimeValuesTable.URI, null, null, null, null)
+ ?.use { cursor ->
val nameColumnIndex =
cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
val valueColumnIndex =
cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
- if (nameColumnIndex == -1 || valueColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- CustomizationProviderClient.Flag(
- name = cursor.getString(nameColumnIndex),
- value = cursor.getInt(valueColumnIndex) == 1,
- )
- )
+ if (nameColumnIndex >= 0 && valueColumnIndex >= 0) {
+ while (cursor.moveToNext()) {
+ when (val name = cursor.getString(nameColumnIndex)) {
+ Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> {
+ putBoolean(name, cursor.getInt(valueColumnIndex) == 1)
+ }
+ }
+ }
}
}
- }
+ }
}
- ?: emptyList()
}
override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
@@ -298,6 +307,10 @@ class CustomizationProviderClientImpl(
return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
}
+ override fun observeRuntimeValues(): Flow<Bundle> {
+ return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() }
+ }
+
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return withContext(backgroundDispatcher) {
context.contentResolver
@@ -375,22 +388,17 @@ class CustomizationProviderClientImpl(
enablementActionIntent =
cursor
.getString(enablementActionIntentColumnIndex)
- ?.toIntent(
- affordanceId = affordanceId,
- ),
+ ?.toIntent(affordanceId = affordanceId),
configureIntent =
cursor
.getString(configureIntentColumnIndex)
- ?.toIntent(
- affordanceId = affordanceId,
- ),
+ ?.toIntent(affordanceId = affordanceId),
)
)
}
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> {
@@ -444,8 +452,7 @@ class CustomizationProviderClientImpl(
}
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
@@ -454,34 +461,24 @@ class CustomizationProviderClientImpl(
}
}
- override suspend fun deleteSelection(
- slotId: String,
- affordanceId: String,
- ) {
+ override suspend fun deleteSelection(slotId: String, affordanceId: String) {
withContext(backgroundDispatcher) {
context.contentResolver.delete(
Contract.LockScreenQuickAffordances.SelectionTable.URI,
"${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" +
" ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" +
" = ?",
- arrayOf(
- slotId,
- affordanceId,
- ),
+ arrayOf(slotId, affordanceId),
)
}
}
- override suspend fun deleteAllSelections(
- slotId: String,
- ) {
+ override suspend fun deleteAllSelections(slotId: String) {
withContext(backgroundDispatcher) {
context.contentResolver.delete(
Contract.LockScreenQuickAffordances.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID,
- arrayOf(
- slotId,
- ),
+ arrayOf(slotId),
)
}
}
@@ -499,9 +496,7 @@ class CustomizationProviderClientImpl(
}
}
- private fun observeUri(
- uri: Uri,
- ): Flow<Unit> {
+ private fun observeUri(uri: Uri): Flow<Unit> {
return callbackFlow {
val observer =
object : ContentObserver(null) {
@@ -522,9 +517,7 @@ class CustomizationProviderClientImpl(
.flowOn(backgroundDispatcher)
}
- private fun String.toIntent(
- affordanceId: String,
- ): Intent? {
+ private fun String.toIntent(affordanceId: String): Intent? {
return try {
Intent.parseUri(this, Intent.URI_INTENT_SCHEME)
} catch (e: URISyntaxException) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 8721c7885265..cb167eddcea9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -198,4 +198,27 @@ object CustomizationProviderContract {
const val VALUE = "value"
}
}
+
+ object RuntimeValuesTable {
+ const val TABLE_NAME = "runtime_values"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ /**
+ * This key corresponds to an Int value, where `1` means `true` and `0` means `false`.
+ *
+ * Whether the shade layout should be wide (true) or narrow (false).
+ *
+ * In a wide layout, notifications and quick settings each take up only half the screen
+ * width (whether they are shown at the same time or not). In a narrow layout, they can each
+ * be as wide as the entire screen.
+ */
+ const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide"
+
+ object Columns {
+ /** String. Unique ID for the value. */
+ const val NAME = "name"
+ /** Type depends on the key name. */
+ const val VALUE = "value"
+ }
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
index f5a955d450e3..47c5bda93c0e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shared.customization.data.content
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.os.Bundle
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -64,11 +65,13 @@ class FakeCustomizationProviderClient(
value = true,
)
),
+ runtimeValues: Bundle = Bundle(),
) : CustomizationProviderClient {
private val slots = MutableStateFlow(slots)
private val affordances = MutableStateFlow(affordances)
private val flags = MutableStateFlow(flags)
+ private val runtimeValues = MutableStateFlow(runtimeValues)
private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
@@ -93,6 +96,10 @@ class FakeCustomizationProviderClient(
return flags.value
}
+ override suspend fun queryRuntimeValues(): Bundle {
+ return runtimeValues.value
+ }
+
override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
return slots.asStateFlow()
}
@@ -101,6 +108,10 @@ class FakeCustomizationProviderClient(
return flags.asStateFlow()
}
+ override fun observeRuntimeValues(): Flow<Bundle> {
+ return runtimeValues.asStateFlow()
+ }
+
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return affordances.value
}
@@ -139,10 +150,7 @@ class FakeCustomizationProviderClient(
}
}
- fun setFlag(
- name: String,
- value: Boolean,
- ) {
+ fun setFlag(name: String, value: Boolean) {
flags.value =
flags.value.toMutableList().apply {
removeIf { it.name == name }
@@ -150,6 +158,10 @@ class FakeCustomizationProviderClient(
}
}
+ fun setRuntimeValues(runtimeValues: Bundle) {
+ this.runtimeValues.value = runtimeValues
+ }
+
fun setSlotCapacity(slotId: String, capacity: Int) {
slots.value =
slots.value.toMutableList().apply {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index a3f40d47cc3f..a487b28b2d0a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -23,12 +23,16 @@ object KeyguardPreviewConstants {
const val MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED = 1988
const val MESSAGE_ID_SLOT_SELECTED = 1337
const val MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES = 214
+ const val MESSAGE_ID_PREVIEW_CLOCK_SIZE = 1119
const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
const val KEY_QUICK_AFFORDANCE_ID = "quick_affordance_id"
const val KEY_SLOT_ID = "slot_id"
+ const val KEY_CLOCK_SIZE = "clock_size"
const val KEYGUARD_QUICK_AFFORDANCE_ID_NONE = "none"
+ const val CLOCK_SIZE_DYNAMIC = "clock_size_dynamic"
+ const val CLOCK_SIZE_SMALL = "clock_size_small"
}
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 7577147a6f16..43131b103e51 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -32784,4 +32784,1180 @@
column="23"/>
</issue>
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt"
+ line="39"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @NonNull Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java"
+ line="300"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" Context context, DeviceConfigProxy proxy) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java"
+ line="75"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public AuthController(Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+ line="716"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @NonNull WindowManager windowManager,"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+ line="721"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val sysuiContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+ line="72"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val configurationController: ConfigurationController,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+ line="74"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java"
+ line="46"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main Resources resources,"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java"
+ line="52"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public BiometricNotificationService(@NonNull Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java"
+ line="148"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt"
+ line="62"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt"
+ line="37"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" c: Context,"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt"
+ line="61"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt"
+ line="52"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt"
+ line="30"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="class CustomTileStatePersisterImpl @Inject constructor(context: Context) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt"
+ line="74"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt"
+ line="18"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt"
+ line="77"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt"
+ line="68"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt"
+ line="38"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt"
+ line="37"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt"
+ line="42"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt"
+ line="95"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt"
+ line="142"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt"
+ line="44"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @NonNull final Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+ line="186"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" ConfigurationController configurationController,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+ line="192"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="class IconBuilder @Inject constructor(private val context: Context) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt"
+ line="27"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt"
+ line="31"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val windowManager: WindowManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+ line="39"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+ line="40"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt"
+ line="41"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of LayoutInflater is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val layoutInflater: LayoutInflater"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt"
+ line="29"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt"
+ line="38"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt"
+ line="38"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt"
+ line="42"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public NotificationGutsManager(Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java"
+ line="137"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt"
+ line="47"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt"
+ line="53"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(val context: Context) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt"
+ line="27"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" ConfigurationController configurationController,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java"
+ line="737"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt"
+ line="63"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" Builder(@Main Resources resources, ViewConfiguration viewConfiguration,"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java"
+ line="563"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public PackageManagerAdapter(Context context) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java"
+ line="45"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt"
+ line="36"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt"
+ line="74"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt"
+ line="41"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt"
+ line="82"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt"
+ line="32"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt"
+ line="36"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" Factory(Context context, QSCustomizerController qsCustomizerController) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java"
+ line="99"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt"
+ line="32"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt"
+ line="36"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt"
+ line="47"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt"
+ line="36"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt"
+ line="33"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of LayoutInflater is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val layoutInflater: LayoutInflater,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt"
+ line="43"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" context: Context"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt"
+ line="35"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt"
+ line="63"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt"
+ line="53"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" windowManager: WindowManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+ line="53"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val applicationContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt"
+ line="60"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt"
+ line="65"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt"
+ line="91"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(context: Context, val shadeViewController: ShadeViewController) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt"
+ line="30"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt"
+ line="31"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt"
+ line="33"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt"
+ line="95"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt"
+ line="33"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Application private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt"
+ line="49"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val configurationController: ConfigurationController,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt"
+ line="43"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt"
+ line="36"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationInteractor, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationInteractor is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @GlobalConfig configurationInteractor: ConfigurationInteractor,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+ line="43"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public AuthController(Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+ line="716"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @NonNull WindowManager windowManager,"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+ line="721"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val sysuiContext: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+ line="72"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of ConfigurationController is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val configurationController: ConfigurationController,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+ line="74"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java"
+ line="46"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main Resources resources,"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java"
+ line="52"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" public BiometricNotificationService(@NonNull Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java"
+ line="148"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" c: Context,"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt"
+ line="61"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt"
+ line="39"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt"
+ line="37"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt"
+ line="42"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @NonNull final Context context,"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+ line="186"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1="class IconBuilder @Inject constructor(private val context: Context) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt"
+ line="27"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val windowManager: WindowManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+ line="40"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt"
+ line="53"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" @Main private val resources: Resources,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" private val context: Context,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+ line="51"
+ column="5"/>
+ </issue>
+
+ <issue
+ id="ShadeDisplayAwareContextChecker"
+ message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+ errorLine1=" windowManager: WindowManager,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+ line="53"
+ column="5"/>
+
</issues>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
index 1b072416faa6..7fb879c02778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
@@ -31,7 +31,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -49,7 +49,7 @@ public class ExpandHelperTest extends SysuiTestCase {
@Rule
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
private ExpandableNotificationRow mRow;
private ExpandHelper mExpandHelper;
private ExpandHelper.Callback mCallback;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 80de087971c5..fa8cdcc4ce2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -24,7 +24,6 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -40,7 +39,6 @@ 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;
@@ -56,15 +54,11 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private AccessibilityManager mAccessibilityManager;
-
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mockSecureSettings);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mockSecureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index 24f3a29e64ee..7e4b6f913770 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -16,16 +16,11 @@
package com.android.systemui.accessibility.floatingmenu;
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
-import android.content.Context;
import android.content.res.Configuration;
-import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -33,7 +28,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.settings.SecureSettings;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -42,8 +36,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
/** Tests for {@link MenuInfoRepository}. */
@@ -54,30 +46,16 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
- private AccessibilityManager mAccessibilityManager;
-
- @Mock
private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
@Mock
private SecureSettings mSecureSettings;
private MenuInfoRepository mMenuInfoRepository;
- private final List<String> mShortcutTargets = new ArrayList<>();
@Before
public void setUp() {
- mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
- mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
- doReturn(mShortcutTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
- anyInt());
-
- mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
- mMockSettingsContentsChanged, mSecureSettings);
- }
-
- @After
- public void tearDown() {
- mShortcutTargets.clear();
+ mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged,
+ mSecureSettings);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 157cccc3d62f..1f48bec97b2d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -83,8 +83,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mSecureSettings);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mSecureSettings);
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 46f076a75116..f7b81cc49f0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -33,7 +33,6 @@ import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.RecyclerView;
@@ -53,7 +52,6 @@ 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;
@@ -80,15 +78,11 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private AccessibilityManager mAccessibilityManager;
-
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- secureSettings);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index ee8ce17cecd4..c1708d175224 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -32,7 +32,6 @@ import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -49,7 +48,6 @@ 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;
@@ -68,9 +66,6 @@ public class MenuViewTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private AccessibilityManager mAccessibilityManager;
-
private SysuiTestableContext mSpyContext;
@Before
@@ -89,8 +84,7 @@ public class MenuViewTest extends SysuiTestCase {
doNothing().when(mSpyContext).startActivity(any());
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- secureSettings);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 0bd00fb0a0e9..73efea764e59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
-import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
import com.android.systemui.model.SysUiState
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
@@ -50,8 +49,8 @@ import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
private const val ON: Int = 1
private const val OFF: Int = 0
@@ -103,7 +102,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemClock,
userTracker,
mainHandler,
- backgroundDelayableExecutor
+ backgroundDelayableExecutor,
)
)
@@ -116,7 +115,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
sysuiState,
fakeBroadcastDispatcher,
mDialogTransitionAnimator,
- fontScalingDialogDelegate
+ fontScalingDialogDelegate,
)
whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
@@ -132,7 +131,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemSettings.getFloatForUser(
Settings.System.FONT_SCALE,
/* def= */ 1.0f,
- userTracker.userId
+ userTracker.userId,
)
assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
@@ -163,7 +162,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemSettings.getFloatForUser(
Settings.System.FONT_SCALE,
/* def= */ 1.0f,
- userTracker.userId
+ userTracker.userId,
)
assertThat(seekBar.getProgress()).isEqualTo(1)
assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
@@ -194,7 +193,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemSettings.getFloatForUser(
Settings.System.FONT_SCALE,
/* def= */ 1.0f,
- userTracker.userId
+ userTracker.userId,
)
assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
assertThat(currentScale)
@@ -211,7 +210,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
secureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
OFF,
- userTracker.userId
+ userTracker.userId,
)
// Default seekbar progress for font size is 1, click start icon to decrease the progress
@@ -224,7 +223,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
secureSettings.getIntForUser(
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
/* def = */ OFF,
- userTracker.userId
+ userTracker.userId,
)
assertThat(currentSettings).isEqualTo(ON)
@@ -236,13 +235,13 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
dialog.show()
val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
- val changeListener = slider.onSeekBarWithIconButtonsChangeListener
+ val seekBarListener = slider.getSeekBarChangeListener()
val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
// Default seekbar progress for font size is 1, simulate dragging to 0 without
// releasing the finger.
- changeListener.onStartTrackingTouch(seekBar)
+ seekBarListener.onStartTrackingTouch(seekBar)
// Update seekbar progress. This will trigger onProgressChanged in the
// OnSeekBarChangeListener and the seekbar could get updated progress value
// in onStopTrackingTouch.
@@ -256,21 +255,12 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemSettings.getFloatForUser(
Settings.System.FONT_SCALE,
/* def= */ 1.0f,
- userTracker.userId
+ userTracker.userId,
)
assertThat(systemScale).isEqualTo(1.0f)
// Simulate releasing the finger from the seekbar.
- changeListener.onStopTrackingTouch(seekBar)
- backgroundDelayableExecutor.runAllReady()
- backgroundDelayableExecutor.advanceClockToNext()
- backgroundDelayableExecutor.runAllReady()
-
- // SeekBar interaction is finalized.
- changeListener.onUserInteractionFinalized(
- seekBar,
- OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
- )
+ seekBarListener.onStopTrackingTouch(seekBar)
backgroundDelayableExecutor.runAllReady()
backgroundDelayableExecutor.advanceClockToNext()
backgroundDelayableExecutor.runAllReady()
@@ -280,7 +270,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
systemSettings.getFloatForUser(
Settings.System.FONT_SCALE,
/* def= */ 1.0f,
- userTracker.userId
+ userTracker.userId,
)
assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
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 9d471f45a293..ad12c61ab5d1 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
@@ -139,13 +139,11 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
private ActivityInfo mActivityInfo;
@Mock
private Drawable mDrawable;
- @Mock
- private HearingDevicesPresetsController mPresetsController;
+
private SystemUIDialog mDialog;
private SystemUIDialog.Factory mDialogFactory;
private HearingDevicesDialogDelegate mDialogDelegate;
private TestableLooper mTestableLooper;
- private final List<CachedBluetoothDevice> mDevices = new ArrayList<>();
@Before
public void setUp() {
@@ -155,7 +153,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
- when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -163,6 +161,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
@@ -170,12 +169,11 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
mContext.setMockPackageManager(mPackageManager);
- mDevices.add(mCachedDevice);
}
@Test
public void clickPairNewDeviceButton_intentActionMatch() {
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithPairNewDeviceButton();
mDialog.show();
getPairNewDeviceButton(mDialog).performClick();
@@ -191,7 +189,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Test
public void onDeviceItemGearClicked_intentActionMatch() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialogDelegate.onDeviceItemGearClicked(mHearingDeviceItem, new View(mContext));
@@ -206,7 +204,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Test
public void onDeviceItemOnClicked_connectedDevice_disconnect() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
@@ -222,7 +220,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(0);
@@ -237,7 +235,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(1);
@@ -247,9 +245,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools()
throws PackageManager.NameNotFoundException {
- when(mPackageManager.queryIntentActivities(
- eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
- List.of(new ResolveInfo()));
+ when(mPackageManager.queryIntentActivities(eq(LIVE_CAPTION_INTENT), anyInt()))
+ .thenReturn(List.of(new ResolveInfo()));
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName,
new String[]{TEST_PKG + "/" + TEST_CLS});
@@ -260,18 +257,18 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT);
when(mDrawable.mutate()).thenReturn(mDrawable);
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(2);
}
@Test
- public void showDialog_noPreset_presetGone() {
- when(mPresetsController.getAllPresetInfo()).thenReturn(new ArrayList<>());
- when(mPresetsController.getActivePresetIndex()).thenReturn(PRESET_INDEX_UNAVAILABLE);
+ public void showDialog_noPreset_presetLayoutGone() {
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
ViewGroup presetLayout = getPresetLayout(mDialog);
@@ -281,11 +278,12 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Test
public void showDialog_presetExist_presetSelected() {
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -295,48 +293,32 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Test
public void onActiveDeviceChanged_presetExist_presetSelected() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+
+ Spinner spinner = getPresetSpinner(mDialog);
+ assertThat(spinner.getSelectedItemPosition()).isEqualTo(-1);
mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
- Spinner spinner = getPresetSpinner(mDialog);
assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
}
+ private void setUpDeviceDialogWithPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ true);
+ }
-
- private void setUpPairNewDeviceDialog() {
- mDialogFactory = new SystemUIDialog.Factory(
- mContext,
- mSystemUIDialogManager,
- mSysUiState,
- getFakeBroadcastDispatcher(),
- mDialogTransitionAnimator
- );
- mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- true,
- TEST_LAUNCH_SOURCE_ID,
- mDialogFactory,
- mActivityStarter,
- mDialogTransitionAnimator,
- mLocalBluetoothManager,
- new Handler(mTestableLooper.getLooper()),
- mAudioManager,
- mUiEventLogger
- );
-
- mDialog = mDialogDelegate.createDialog();
+ private void setUpDeviceDialogWithoutPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ false);
}
- private void setUpDeviceListDialog() {
+ private void setUpDeviceDialog(boolean showPairNewDevice) {
mDialogFactory = new SystemUIDialog.Factory(
mContext,
mSystemUIDialogManager,
@@ -345,8 +327,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mDialogTransitionAnimator
);
mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- false,
+ showPairNewDevice,
TEST_LAUNCH_SOURCE_ID,
mDialogFactory,
mActivityStarter,
@@ -356,15 +337,14 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mAudioManager,
mUiEventLogger
);
-
mDialog = mDialogDelegate.createDialog();
- mDialogDelegate.setHearingDevicesPresetsController(mPresetsController);
}
private BluetoothHapPresetInfo getTestPresetInfo() {
BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
when(info.getName()).thenReturn(TEST_PRESET_NAME);
when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(info.isAvailable()).thenReturn(true);
return info;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
index 2ac5d105ba99..c9779c90c9aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
@@ -21,10 +21,10 @@ 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.anyList;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.never;
import static java.util.Collections.emptyList;
@@ -39,7 +39,6 @@ import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +52,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link HearingDevicesPresetsController}. */
@@ -62,6 +62,7 @@ import java.util.concurrent.Executor;
public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
private static final int TEST_PRESET_INDEX = 1;
+ private static final int TEST_UPDATED_PRESET_INDEX = 2;
private static final String TEST_PRESET_NAME = "test_preset";
private static final int TEST_HAP_GROUP_ID = 1;
private static final int TEST_REASON = 1024;
@@ -74,14 +75,13 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
@Mock
private HapClientProfile mHapClientProfile;
@Mock
- private CachedBluetoothDevice mCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedDevice;
@Mock
- private CachedBluetoothDevice mSubCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedMemberDevice;
@Mock
- private BluetoothDevice mBluetoothDevice;
+ private BluetoothDevice mDevice;
@Mock
- private BluetoothDevice mSubBluetoothDevice;
-
+ private BluetoothDevice mMemberDevice;
@Mock
private HearingDevicesPresetsController.PresetCallback mCallback;
@@ -91,15 +91,19 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
public void setUp() {
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mHapClientProfile.isProfileReady()).thenReturn(true);
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
- when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
- when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+ when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
mController = new HearingDevicesPresetsController(mProfileManager, mCallback);
+ mController.setDevice(mCachedDevice);
}
@Test
public void onServiceConnected_callExpectedCallback() {
+ preparePresetInfo(/* isValid= */ true);
+
mController.onServiceConnected();
verify(mHapClientProfile).registerCallback(any(Executor.class),
@@ -108,115 +112,129 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
}
@Test
- public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() {
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList());
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void setDevice_nonHapDevice_getEmptyListAndInvalidActiveIndex() {
+ when(mCachedDevice.getProfiles()).thenReturn(emptyList());
+ preparePresetInfo(/* isValid= */ true);
+
+ mController.setDevice(mCachedDevice);
assertThat(mController.getAllPresetInfo()).isEmpty();
+ assertThat(mController.getActivePresetIndex()).isEqualTo(
+ BluetoothHapClient.PRESET_INDEX_UNAVAILABLE);
}
@Test
- public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_containsOnlyNotAvailablePresetInfo_getEmptyList() {
+ preparePresetInfo(/* isValid= */ false);
+
+ mController.refreshPresetInfo();
assertThat(mController.getAllPresetInfo()).isEmpty();
}
@Test
- public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo);
+ mController.refreshPresetInfo();
+
+ List<BluetoothHapPresetInfo> presetInfos = mController.getAllPresetInfo();
+ assertThat(presetInfos.size()).isEqualTo(1);
+ assertThat(presetInfos).contains(infos.getFirst());
}
@Test
- public void getActivePresetIndex_getExpectedIndex() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void refreshPresetInfo_getExpectedIndex() {
+ preparePresetInfo(/* isValid= */ true);
+
+ mController.refreshPresetInfo();
assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX);
}
@Test
- public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void refreshPresetInfo_callbackIsCalledWhenNeeded() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
+
+ mController.refreshPresetInfo();
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
+
+ Mockito.reset(mCallback);
+ mController.refreshPresetInfo();
+
+ verify(mCallback, never()).onPresetInfoUpdated(anyList(), anyInt());
+
+ Mockito.reset(mCallback);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_UPDATED_PRESET_INDEX);
+ mController.refreshPresetInfo();
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_UPDATED_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPresetSelected_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON);
+ mController.onPresetSelected(mDevice, TEST_PRESET_INDEX, TEST_REASON);
- verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX));
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
}
@Test
- public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void onPresetInfoChanged_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON);
+ mController.onPresetInfoChanged(mDevice, infos, TEST_REASON);
- verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX);
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
}
@Test
public void onPresetSelectionFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON);
+ mController.onPresetSelectionFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
public void onSetPresetNameFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON);
+ mController.onSetPresetNameFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
- public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
+ public void onPresetSelectionForGroupFailed_callSelectPresetIndependently() {
mController.selectPreset(TEST_PRESET_INDEX);
Mockito.reset(mHapClientProfile);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
-
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
+ public void registerHapCallback_profileNotReady_addServiceListener() {
+ when(mHapClientProfile.isProfileReady()).thenReturn(false);
+
+ mController.registerHapCallback();
+
+ verify(mProfileManager).addServiceListener(mController);
+ verify(mHapClientProfile, never()).registerCallback(any(Executor.class),
+ any(BluetoothHapClient.Callback.class));
+ }
+
+ @Test
public void registerHapCallback_callHapRegisterCallback() {
mController.registerHapCallback();
@@ -233,9 +251,8 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
@Test
public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
@@ -243,28 +260,34 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
}
@Test
- public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(
+ public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
- public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
+ }
+
+ private List<BluetoothHapPresetInfo> preparePresetInfo(boolean isValid) {
+ BluetoothHapPresetInfo info = getHapPresetInfo(isValid);
+ List<BluetoothHapPresetInfo> infos = List.of(info);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(infos);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+ return infos;
}
private BluetoothHapPresetInfo getHapPresetInfo(boolean available) {
@@ -274,12 +297,4 @@ public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
when(info.isAvailable()).thenReturn(available);
return info;
}
-
- private void setValidHearingDeviceSupportHap() {
- LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class);
- List<LocalBluetoothProfile> profiles = List.of(hapClientProfile);
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles);
-
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- }
}
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 91f9cce5b69b..b8d4bb4b8e77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -667,6 +667,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
faceProps,
wakefulnessLifecycle,
userManager,
+ null /* authContextPlugins */,
lockPatternUtils,
interactionJankMonitor,
{ promptSelectorInteractor },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 2dcbdc80f695..acc97a9f8642 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -117,6 +118,7 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
@RunWith(AndroidJUnit4.class)
@@ -181,6 +183,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
+ private ArgumentCaptor<KeyguardManager.KeyguardLockedStateListener> mKeyguardLockedStateCaptor;
+ @Captor
private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
private ArgumentCaptor<Integer> mModalityCaptor;
@@ -191,6 +195,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private VibratorHelper mVibratorHelper;
@Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
private MSDLPlayer mMSDLPlayer;
private TestableContext mContextSpy;
@@ -271,6 +277,9 @@ public class AuthControllerTest extends SysuiTestCase {
mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
+ verify(mKeyguardManager).addKeyguardLockedStateListener(any(),
+ mKeyguardLockedStateCaptor.capture());
+
// Ensures that the operations posted on the handler get executed.
waitForIdleSync();
}
@@ -976,6 +985,18 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Test
+ public void testCloseDialog_whenDeviceLocks() throws Exception {
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+
+ mKeyguardLockedStateCaptor.getValue().onKeyguardLockedStateChanged(
+ true /* isKeyguardLocked */);
+
+ verify(mReceiver).onDialogDismissed(
+ eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+ eq(null) /* credentialAttestation */);
+ }
+
+ @Test
public void testShowDialog_whenOwnerNotInForeground() {
PromptInfo promptInfo = createTestPromptInfo();
promptInfo.setAllowBackgroundAuthentication(false);
@@ -1187,11 +1208,12 @@ public class AuthControllerTest extends SysuiTestCase {
TestableAuthController(Context context) {
super(context, null /* applicationCoroutineScope */,
mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
- mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager,
+ mFingerprintManager, mFaceManager, Optional.empty(),
+ () -> mUdfpsController, mDisplayManager,
mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
() -> mLogContextInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
- mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
+ mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, mKeyguardManager,
mLazyViewCapture, mMSDLPlayer);
}
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 21c6583d4e84..aeea99be40dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -365,6 +365,25 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
+ public void showUdfpsOverlay_invokedTwice_doesNotNotifyListenerSecondTime() throws RemoteException {
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ TEST_REQUEST_ID, mOpticalProps.sensorId);
+
+ reset(mFingerprintManager);
+
+ // Second attempt should do nothing
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+ verify(mFingerprintManager, never()).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+ TEST_REQUEST_ID, mOpticalProps.sensorId);
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 4e64c50a3253..297aee5c84c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.MotionEvent
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -24,6 +26,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -39,6 +42,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -57,6 +62,8 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() {
private val testScope: TestScope = kosmos.testScope
private val authController: AuthController = kosmos.authController
+ private val fingerprintManager: FingerprintManager = kosmos.fingerprintManager
+ @Mock private lateinit var fingerprintSensorProperties: FingerprintSensorPropertiesInternal
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@@ -122,6 +129,20 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() {
context.orCreateTestableResources.removeOverride(R.dimen.pixel_pitch)
}
+ @Test
+ fun testSetIgnoreDisplayTouches() =
+ testScope.runTest {
+ createUdfpsOverlayInteractor()
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ whenever(authController.udfpsProps).thenReturn(listOf(fingerprintSensorProperties))
+
+ underTest.setHandleTouches(false)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(true))
+
+ underTest.setHandleTouches(true)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(false))
+ }
+
private fun createUdfpsOverlayInteractor() {
underTest = kosmos.udfpsOverlayInteractor
testScope.runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index 97f2e56e6eec..b33a83cf202a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -59,6 +59,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.qs.ui.viewmodel.fakeQsSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.sceneContainerViewModelFactory
@@ -67,6 +68,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -114,7 +116,13 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
private val Kosmos.initialSceneKey by Fixture { Scenes.Bouncer }
private val Kosmos.sceneContainerConfig by Fixture {
val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0)
- SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances)
+ SceneContainerConfig(
+ sceneKeys,
+ initialSceneKey,
+ SceneContainerTransitions,
+ emptyList(),
+ navigationDistances,
+ )
}
private val view = mock<View>()
@@ -181,8 +189,10 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
Scenes.Bouncer to bouncerScene,
),
initialSceneKey = Scenes.Bouncer,
+ sceneTransitions = SceneContainerTransitions,
overlayByKey = emptyMap(),
dataSourceDelegator = kosmos.sceneDataSourceDelegator,
+ qsSceneAdapter = { kosmos.fakeQsSceneAdapter },
)
}
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index 3bf4460e0a4a..94f6769ba406 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -34,6 +34,11 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
@@ -212,6 +217,25 @@ class BouncerSceneContentViewModelTest : SysuiTestCase() {
assertThat(isFoldSplitRequired).isTrue()
}
+ @Test
+ fun onUiDestroyed_clearsPendingDismissAction() =
+ kosmos.runTest {
+ val dismissAction by collectLastValue(fakeKeyguardRepository.dismissAction)
+ fakeKeyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(dismissAction).isNotEqualTo(DismissAction.None)
+
+ underTest.onUiDestroyed()
+
+ assertThat(dismissAction).isEqualTo(DismissAction.None)
+ }
+
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
return listOf(None, Pin, Password, Pattern, Sim)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index cecb5251b6e2..01baadda7c87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -19,7 +19,6 @@ package com.android.systemui.common.ui.view;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -129,16 +128,43 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
}
@Test
- public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+ public void setProgress_onProgressChangedAndOnUserInteractionFinalized() {
reset(mOnSeekBarChangeListener);
mIconDiscreteSliderLinearLayout.setProgress(1);
+ // If users are changing seekbar progress without touching the seekbar or clicking the
+ // buttons, trigger onUserInteractionFinalized.
verify(mOnSeekBarChangeListener).onProgressChanged(
eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+ verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+ /* seekBar= */ any(),
+ eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
+ }
+
+ @Test
+ public void setProgressToSeekBarByTouch_onUserInteractionFinalizedAfterTouchEnds() {
+ reset(mOnSeekBarChangeListener);
+ final SeekBarWithIconButtonsView.SeekBarChangeListener seekBarChangeListener =
+ mIconDiscreteSliderLinearLayout.getSeekBarChangeListener();
+ final SeekBar seekBar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+ // Simulate changing seekbar progress by touch
+ seekBarChangeListener.onStartTrackingTouch(seekBar);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ verify(mOnSeekBarChangeListener).onProgressChanged(
+ eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
- /* seekBar= */any(), /* control= */ anyInt());
+ /* seekBar= */ any(),
+ eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
+
+ // Notify onUserInteractionFinalized after touch ends
+ seekBarChangeListener.onStopTrackingTouch(seekBar);
+ verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+ /* seekBar= */ any(),
+ eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index 596db0767867..f1c58a2aeac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
+import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
@@ -117,7 +118,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[0],
rank = 0,
userSerialNumber = 0,
- spanY = 3,
+ spanY = SpanValue.Fixed(3),
)
verify(communalWidgetDao)
.addWidget(
@@ -125,7 +126,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[1],
rank = 1,
userSerialNumber = 0,
- spanY = 3,
+ spanY = SpanValue.Fixed(3),
)
verify(communalWidgetDao)
.addWidget(
@@ -133,7 +134,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = defaultWidgets[2],
rank = 2,
userSerialNumber = 0,
- spanY = 3,
+ spanY = SpanValue.Fixed(3),
)
}
@@ -155,7 +156,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() {
componentName = any(),
rank = anyInt(),
userSerialNumber = anyInt(),
- spanY = anyInt(),
+ spanY = any(),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 06b710eb86eb..b66727e492cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
+import android.content.res.mainResources
import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -29,6 +30,7 @@ import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.DisabledReason
@@ -53,7 +55,8 @@ import org.mockito.ArgumentMatchers.eq
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
private val testScope = kosmos.testScope
private lateinit var underTest: CommunalSettingsRepository
@@ -67,6 +70,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
}
@EnableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun getFlagEnabled_bothEnabled() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -74,7 +78,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.getFlagEnabled()).isTrue()
}
- @DisableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
fun getFlagEnabled_bothDisabled() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
@@ -82,7 +86,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.getFlagEnabled()).isFalse()
}
- @DisableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
fun getFlagEnabled_onlyClassicFlagEnabled() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -91,6 +95,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
}
@EnableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun getFlagEnabled_onlyTrunkFlagEnabled() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
@@ -98,6 +103,57 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.getFlagEnabled()).isFalse()
}
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @DisableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun getFlagEnabled_mobileConfigEnabled() {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
+
+ assertThat(underTest.getFlagEnabled()).isTrue()
+ }
+
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB)
+ @Test
+ fun getFlagEnabled_onlyMobileConfigEnabled() {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
+
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
+
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @DisableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun getFlagEnabled_onlyMobileFlagEnabled() {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ false,
+ )
+
+ assertThat(underTest.getFlagEnabled()).isFalse()
+ }
+
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @DisableFlags(FLAG_COMMUNAL_HUB)
+ @Test
+ fun getFlagEnabled_oldFlagIgnored() {
+ // New config flag enabled.
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ true,
+ )
+
+ // Old config flag disabled.
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+
+ assertThat(underTest.getFlagEnabled()).isTrue()
+ }
+
@EnableFlags(FLAG_COMMUNAL_HUB)
@Test
fun secondaryUserIsInvalid() =
@@ -134,7 +190,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
kosmos.fakeSettings.putIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
0,
- PRIMARY_USER.id
+ PRIMARY_USER.id,
)
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isFalse()
@@ -143,14 +199,14 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
kosmos.fakeSettings.putIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
1,
- SECONDARY_USER.id
+ SECONDARY_USER.id,
)
assertThat(enabledState?.enabled).isFalse()
kosmos.fakeSettings.putIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
1,
- PRIMARY_USER.id
+ PRIMARY_USER.id,
)
assertThat(enabledState?.enabled).isTrue()
}
@@ -201,7 +257,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
kosmos.fakeSettings.putIntForUser(
Settings.Secure.GLANCEABLE_HUB_ENABLED,
0,
- PRIMARY_USER.id
+ PRIMARY_USER.id,
)
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
@@ -228,7 +284,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
kosmos.fakeSettings.putIntForUser(
GLANCEABLE_HUB_BACKGROUND_SETTING,
type.value,
- PRIMARY_USER.id
+ PRIMARY_USER.id,
)
assertWithMessage(
"Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" +
@@ -253,12 +309,6 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
val WORK_PROFILE =
- UserInfo(
- 10,
- "work",
- /* iconPath= */ "",
- /* flags= */ 0,
- USER_TYPE_PROFILE_MANAGED,
- )
+ UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
index 55d7d08e8519..335e39902983 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
@@ -24,11 +24,15 @@ import android.content.ComponentName
import android.content.applicationContext
import android.graphics.Bitmap
import android.os.UserHandle
+import android.os.UserManager
import android.os.userManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
+import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
@@ -40,11 +44,15 @@ import com.android.systemui.communal.data.db.defaultWidgetPopulation
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toByteArray
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.widgetConfiguratorFail
import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
-import com.android.systemui.coroutines.collectLastValue
+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.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
@@ -52,48 +60,55 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
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.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
- @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
- @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
- @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
- @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
- @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
- @Mock private lateinit var backupManager: BackupManager
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalWidgetRepositoryLocalImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val appWidgetHost = mock<CommunalAppWidgetHost>()
+ private val providerInfoA = mock<AppWidgetProviderInfo>()
+ private val providerInfoB = mock<AppWidgetProviderInfo>()
+ private val providerInfoC = mock<AppWidgetProviderInfo>()
private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
private val componentNameCaptor = argumentCaptor<ComponentName>()
- private lateinit var backupUtils: CommunalBackupUtils
- private lateinit var logBuffer: LogBuffer
- private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
- private lateinit var fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>>
+ private val Kosmos.communalWidgetHost by
+ Kosmos.Fixture {
+ mock<CommunalWidgetHost> { on { appWidgetProviders } doReturn fakeProviders }
+ }
+ private val Kosmos.communalWidgetDao by
+ Kosmos.Fixture { mock<CommunalWidgetDao> { on { getWidgets() } doReturn fakeWidgets } }
+
+ private val Kosmos.backupManager by Kosmos.Fixture { mock<BackupManager>() }
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val packageChangeRepository = kosmos.fakePackageChangeRepository
- private val userManager = kosmos.userManager
+ private val Kosmos.backupUtils: CommunalBackupUtils by
+ Kosmos.Fixture { CommunalBackupUtils(applicationContext) }
+
+ private val Kosmos.logBuffer: LogBuffer by
+ Kosmos.Fixture { logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest") }
+
+ private val Kosmos.fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>> by
+ Kosmos.Fixture { MutableStateFlow(emptyMap()) }
+
+ private val Kosmos.fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>> by
+ Kosmos.Fixture { MutableStateFlow(emptyMap()) }
private val mainUser = UserHandle(0)
private val workProfile = UserHandle(10)
@@ -105,48 +120,49 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
"com.android.fake/WidgetProviderC",
)
- private lateinit var underTest: CommunalWidgetRepositoryLocalImpl
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- fakeWidgets = MutableStateFlow(emptyMap())
- fakeProviders = MutableStateFlow(emptyMap())
- logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest")
- backupUtils = CommunalBackupUtils(kosmos.applicationContext)
-
- setAppWidgetIds(emptyList())
-
- overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
-
- whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
- whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
- whenever(userManager.mainUser).thenReturn(mainUser)
-
- restoreUser(mainUser)
-
- underTest =
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
CommunalWidgetRepositoryLocalImpl(
appWidgetHost,
testScope.backgroundScope,
- kosmos.testDispatcher,
+ testDispatcher,
communalWidgetHost,
communalWidgetDao,
logBuffer,
backupManager,
backupUtils,
- packageChangeRepository,
+ fakePackageChangeRepository,
userManager,
- kosmos.defaultWidgetPopulation,
+ defaultWidgetPopulation,
)
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.userManager = mock<UserManager> { on { mainUser } doReturn mainUser }
+ setAppWidgetIds(emptyList())
+ overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
+ restoreUser(mainUser)
}
@Test
fun communalWidgets_queryWidgetsFromDb() =
- testScope.runTest {
+ kosmos.runTest {
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry =
- CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0, 3)
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pk_name/cls_name",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ )
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -158,7 +174,12 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
appWidgetId = communalWidgetItemEntry.widgetId,
providerInfo = providerInfoA,
rank = communalItemRankEntry.rank,
- spanY = communalWidgetItemEntry.spanY,
+ spanY =
+ if (communalResponsiveGrid()) {
+ communalWidgetItemEntry.spanYNew
+ } else {
+ communalWidgetItemEntry.spanY
+ },
)
)
@@ -168,18 +189,50 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() =
- testScope.runTest {
+ kosmos.runTest {
// Set up 4 widgets, but widget 3 and 4 don't have matching providers
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pk_1/cls_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = 2,
+ componentName = "pk_2/cls_2",
+ itemId = 2L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
CommunalItemRank(uid = 3L, rank = 3) to
- CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0, 3),
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = 3,
+ componentName = "pk_3/cls_3",
+ itemId = 3L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
CommunalItemRank(uid = 4L, rank = 4) to
- CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0, 3),
+ CommunalWidgetItem(
+ uid = 4L,
+ widgetId = 4,
+ componentName = "pk_4/cls_4",
+ itemId = 4L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
)
fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
@@ -191,27 +244,43 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
)
}
@Test
fun communalWidgets_updatedWhenProvidersUpdate() =
- testScope.runTest {
+ kosmos.runTest {
// Set up widgets and providers
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pk_1/cls_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = 2,
+ componentName = "pk_2/cls_2",
+ itemId = 2L,
+ userSerialNumber = 0,
+ spanY = 6,
+ spanYNew = 2,
+ ),
)
fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
@@ -224,13 +293,13 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 2 else 6,
),
)
@@ -245,20 +314,20 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
// Verify that provider info updated
providerInfo = providerInfoC,
rank = 1,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
rank = 2,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 2 else 6,
),
)
}
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
- testScope.runTest {
+ kosmos.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val rank = 1
@@ -275,7 +344,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao)
+ .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -283,7 +353,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
- testScope.runTest {
+ kosmos.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val rank = 1
@@ -301,7 +371,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -310,7 +380,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
- testScope.runTest {
+ kosmos.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val rank = 1
@@ -330,7 +400,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
verify(communalWidgetDao, never())
- .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -339,7 +409,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
- testScope.runTest {
+ kosmos.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val rank = 1
@@ -356,7 +426,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao)
+ .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -364,7 +435,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun deleteWidget_deleteFromDbTrue_alsoDeleteFromHost() =
- testScope.runTest {
+ kosmos.runTest {
val id = 1
whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(true)
underTest.deleteWidget(id)
@@ -379,7 +450,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun deleteWidget_deleteFromDbFalse_doesNotDeleteFromHost() =
- testScope.runTest {
+ kosmos.runTest {
val id = 1
whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(false)
underTest.deleteWidget(id)
@@ -394,7 +465,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
- testScope.runTest {
+ kosmos.runTest {
val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
underTest.updateWidgetOrder(widgetIdToRankMap)
runCurrent()
@@ -407,7 +478,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_deleteStateFileIfRestoreFails() =
- testScope.runTest {
+ kosmos.runTest {
// Write a state file that is invalid, and verify it is written
backupUtils.writeBytesToDisk(byteArrayOf(1, 2, 3, 4, 5, 6))
assertThat(backupUtils.fileExists()).isTrue()
@@ -422,7 +493,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_deleteStateFileAfterWidgetsRestored() =
- testScope.runTest {
+ kosmos.runTest {
// Write a state file, and verify it is written
backupUtils.writeBytesToDisk(fakeState.toByteArray())
assertThat(backupUtils.fileExists()).isTrue()
@@ -443,7 +514,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_restoredWidgetsNotRegisteredWithHostAreSkipped() =
- testScope.runTest {
+ kosmos.runTest {
// Write fake state to file
backupUtils.writeBytesToDisk(fakeState.toByteArray())
@@ -470,7 +541,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_registeredWidgetsNotRestoredAreRemoved() =
- testScope.runTest {
+ kosmos.runTest {
// Write fake state to file
backupUtils.writeBytesToDisk(fakeState.toByteArray())
@@ -504,7 +575,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_onlySomeWidgetsGotNewIds() =
- testScope.runTest {
+ kosmos.runTest {
// Write fake state to file
backupUtils.writeBytesToDisk(fakeState.toByteArray())
@@ -536,7 +607,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_undefinedUser_restoredAsMain() =
- testScope.runTest {
+ kosmos.runTest {
// Write two widgets to file, both of which have user serial number undefined.
val fakeState =
CommunalHubState().apply {
@@ -584,7 +655,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
- testScope.runTest {
+ kosmos.runTest {
// Write fake state to file
backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
@@ -610,7 +681,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
- testScope.runTest {
+ kosmos.runTest {
// Write fake state to file
backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
@@ -649,7 +720,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
componentNameCaptor.capture(),
eq(2),
eq(testUserSerialNumber(workProfile)),
- anyInt(),
+ any(),
)
assertThat(componentNameCaptor.firstValue)
@@ -658,13 +729,29 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
@Test
fun pendingWidgets() =
- testScope.runTest {
+ kosmos.runTest {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pk_1/cls_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = 2,
+ componentName = "pk_2/cls_2",
+ itemId = 2L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ ),
)
// Widget 1 is installed
@@ -672,7 +759,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
// Widget 2 is pending install
val fakeIcon = mock<Bitmap>()
- packageChangeRepository.setInstallSessions(
+ fakePackageChangeRepository.setInstallSessions(
listOf(
PackageInstallSession(
sessionId = 1,
@@ -690,7 +777,7 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
CommunalWidgetContentModel.Pending(
appWidgetId = 2,
@@ -698,23 +785,31 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
componentName = ComponentName("pk_2", "cls_2"),
icon = fakeIcon,
user = mainUser,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
),
)
}
@Test
fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() =
- testScope.runTest {
+ kosmos.runTest {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3)
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = 1,
+ componentName = "pk_1/cls_1",
+ itemId = 1L,
+ userSerialNumber = 0,
+ spanY = 3,
+ spanYNew = 1,
+ )
)
// Widget 1 is pending install
val fakeIcon = mock<Bitmap>()
- packageChangeRepository.setInstallSessions(
+ fakePackageChangeRepository.setInstallSessions(
listOf(
PackageInstallSession(
sessionId = 1,
@@ -734,12 +829,12 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
)
)
// Package for widget 1 finished installing
- packageChangeRepository.setInstallSessions(emptyList())
+ fakePackageChangeRepository.setInstallSessions(emptyList())
// Provider info for widget 1 becomes available
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -752,15 +847,32 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
appWidgetId = 1,
providerInfo = providerInfoA,
rank = 1,
- spanY = 3,
+ spanY = if (communalResponsiveGrid()) 1 else 3,
)
)
}
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
- fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() =
- testScope.runTest {
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_fixed() =
+ kosmos.runTest {
+ val widgetId = 1
+ val newSpanY = 6
+ val widgetIdToRankMap = emptyMap<Int, Int>()
+
+ underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
+ runCurrent()
+
+ verify(communalWidgetDao)
+ .resizeWidget(widgetId, SpanValue.Fixed(newSpanY), widgetIdToRankMap)
+ verify(backupManager).dataChanged()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+ fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_responsive() =
+ kosmos.runTest {
val widgetId = 1
val newSpanY = 6
val widgetIdToRankMap = emptyMap<Int, Int>()
@@ -768,7 +880,8 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
runCurrent()
- verify(communalWidgetDao).resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
+ verify(communalWidgetDao)
+ .resizeWidget(widgetId, SpanValue.Responsive(newSpanY), widgetIdToRankMap)
verify(backupManager).dataChanged()
}
@@ -784,13 +897,19 @@ class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
}
private fun restoreUser(user: UserHandle) {
- whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
+ whenever(kosmos.backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
.thenReturn(user)
- whenever(userManager.getUserSerialNumber(user.identifier))
+ whenever(kosmos.userManager.getUserSerialNumber(user.identifier))
.thenReturn(testUserSerialNumber(user))
}
- private companion object {
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ }
+
val PROVIDER_INFO_REQUIRES_CONFIGURATION =
AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
val PROVIDER_INFO_CONFIGURATION_OPTIONAL =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 611a61a6282c..b9e646fee98f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -24,14 +24,16 @@ import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
@@ -96,6 +98,8 @@ import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* This class of test cases assume that communal is enabled. For disabled cases, see
@@ -103,8 +107,8 @@ import org.mockito.MockitoAnnotations
*/
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
-class CommunalInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mainUser: UserInfo
@Mock private lateinit var secondaryUser: UserInfo
@@ -129,6 +133,10 @@ class CommunalInteractorTest : SysuiTestCase() {
private lateinit var underTest: CommunalInteractor
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -262,71 +270,84 @@ class CommunalInteractorTest : SysuiTestCase() {
assertThat(widgetContent!![2].appWidgetId).isEqualTo(3)
}
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspaceDynamicSizing_oneCard_fullSize() =
testSmartspaceDynamicSizing(
totalTargets = 1,
- expectedSizes = listOf(CommunalContentSize.FULL),
+ expectedSizes = listOf(CommunalContentSize.FixedSize.FULL),
)
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspace_dynamicSizing_twoCards_halfSize() =
testSmartspaceDynamicSizing(
totalTargets = 2,
- expectedSizes = listOf(CommunalContentSize.HALF, CommunalContentSize.HALF),
+ expectedSizes =
+ listOf(CommunalContentSize.FixedSize.HALF, CommunalContentSize.FixedSize.HALF),
)
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspace_dynamicSizing_threeCards_thirdSize() =
testSmartspaceDynamicSizing(
totalTargets = 3,
expectedSizes =
listOf(
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
),
)
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspace_dynamicSizing_fourCards_threeThirdSizeAndOneFullSize() =
testSmartspaceDynamicSizing(
totalTargets = 4,
expectedSizes =
listOf(
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.FULL,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.FULL,
),
)
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspace_dynamicSizing_fiveCards_threeThirdAndTwoHalfSize() =
testSmartspaceDynamicSizing(
totalTargets = 5,
expectedSizes =
listOf(
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.HALF,
- CommunalContentSize.HALF,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.HALF,
+ CommunalContentSize.FixedSize.HALF,
),
)
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun smartspace_dynamicSizing_sixCards_allThirdSize() =
testSmartspaceDynamicSizing(
totalTargets = 6,
expectedSizes =
listOf(
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
- CommunalContentSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
+ CommunalContentSize.FixedSize.THIRD,
),
)
@@ -383,7 +404,9 @@ class CommunalInteractorTest : SysuiTestCase() {
assertThat(umoContent?.size).isEqualTo(0)
}
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun ongoing_shouldOrderAndSizeByTimestamp() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
@@ -410,15 +433,15 @@ class CommunalInteractorTest : SysuiTestCase() {
assertThat(ongoingContent?.size).isEqualTo(4)
assertThat(ongoingContent?.get(0)?.key)
.isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
- assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.HALF)
+ assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
assertThat(ongoingContent?.get(1)?.key)
.isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
- assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.HALF)
+ assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
- assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.HALF)
+ assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
assertThat(ongoingContent?.get(3)?.key)
.isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
- assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.HALF)
+ assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
}
@Test
@@ -1082,6 +1105,7 @@ class CommunalInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_withoutUpdatingOrder() =
testScope.runTest {
val userInfos = listOf(MAIN_USER_INFO)
@@ -1094,45 +1118,97 @@ class CommunalInteractorTest : SysuiTestCase() {
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
)
widgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
)
widgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
)
val widgetContent by collectLastValue(underTest.widgetContent)
assertThat(widgetContent?.map { it.appWidgetId to it.size })
.containsExactly(
- 1 to CommunalContentSize.HALF,
- 2 to CommunalContentSize.HALF,
- 3 to CommunalContentSize.HALF,
+ 1 to CommunalContentSize.FixedSize.HALF,
+ 2 to CommunalContentSize.FixedSize.HALF,
+ 3 to CommunalContentSize.FixedSize.HALF,
)
.inOrder()
- underTest.resizeWidget(2, CommunalContentSize.FULL.span, emptyMap())
+ underTest.resizeWidget(2, CommunalContentSize.FixedSize.FULL.span, emptyMap())
// Widget 2 should have been resized to FULL
assertThat(widgetContent?.map { it.appWidgetId to it.size })
.containsExactly(
- 1 to CommunalContentSize.HALF,
- 2 to CommunalContentSize.FULL,
- 3 to CommunalContentSize.HALF,
+ 1 to CommunalContentSize.FixedSize.HALF,
+ 2 to CommunalContentSize.FixedSize.FULL,
+ 3 to CommunalContentSize.FixedSize.HALF,
+ )
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+ fun resizeWidget_withoutUpdatingOrder_responsive() =
+ testScope.runTest {
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ runCurrent()
+
+ // Widgets available.
+ widgetRepository.addWidget(
+ appWidgetId = 1,
+ userId = MAIN_USER_INFO.id,
+ rank = 0,
+ spanY = 1,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 2,
+ userId = MAIN_USER_INFO.id,
+ rank = 1,
+ spanY = 1,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 3,
+ userId = MAIN_USER_INFO.id,
+ rank = 2,
+ spanY = 1,
+ )
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.Responsive(1),
+ 2 to CommunalContentSize.Responsive(1),
+ 3 to CommunalContentSize.Responsive(1),
+ )
+ .inOrder()
+
+ underTest.resizeWidget(appWidgetId = 2, spanY = 5, widgetIdToRankMap = emptyMap())
+
+ // Widget 2 should have been resized to FULL
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.Responsive(1),
+ 2 to CommunalContentSize.Responsive(5),
+ 3 to CommunalContentSize.Responsive(1),
)
.inOrder()
}
@Test
@EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun resizeWidget_andUpdateOrder() =
testScope.runTest {
val userInfos = listOf(MAIN_USER_INFO)
@@ -1145,39 +1221,98 @@ class CommunalInteractorTest : SysuiTestCase() {
appWidgetId = 1,
userId = MAIN_USER_INFO.id,
rank = 0,
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 2,
+ userId = MAIN_USER_INFO.id,
+ rank = 1,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
+ )
+ widgetRepository.addWidget(
+ appWidgetId = 3,
+ userId = MAIN_USER_INFO.id,
+ rank = 2,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
+ )
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 1 to CommunalContentSize.FixedSize.HALF,
+ 2 to CommunalContentSize.FixedSize.HALF,
+ 3 to CommunalContentSize.FixedSize.HALF,
+ )
+ .inOrder()
+
+ underTest.resizeWidget(
+ 2,
+ CommunalContentSize.FixedSize.FULL.span,
+ mapOf(2 to 0, 1 to 1),
+ )
+
+ // Widget 2 should have been resized to FULL and moved to the front of the list
+ assertThat(widgetContent?.map { it.appWidgetId to it.size })
+ .containsExactly(
+ 2 to CommunalContentSize.FixedSize.FULL,
+ 1 to CommunalContentSize.FixedSize.HALF,
+ 3 to CommunalContentSize.FixedSize.HALF,
+ )
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+ fun resizeWidget_andUpdateOrder_responsive() =
+ testScope.runTest {
+ val userInfos = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+ runCurrent()
+
+ // Widgets available.
+ widgetRepository.addWidget(
+ appWidgetId = 1,
+ userId = MAIN_USER_INFO.id,
+ rank = 0,
+ spanY = 1,
)
widgetRepository.addWidget(
appWidgetId = 2,
userId = MAIN_USER_INFO.id,
rank = 1,
- spanY = CommunalContentSize.HALF.span,
+ spanY = 1,
)
widgetRepository.addWidget(
appWidgetId = 3,
userId = MAIN_USER_INFO.id,
rank = 2,
- spanY = CommunalContentSize.HALF.span,
+ spanY = 1,
)
val widgetContent by collectLastValue(underTest.widgetContent)
assertThat(widgetContent?.map { it.appWidgetId to it.size })
.containsExactly(
- 1 to CommunalContentSize.HALF,
- 2 to CommunalContentSize.HALF,
- 3 to CommunalContentSize.HALF,
+ 1 to CommunalContentSize.Responsive(1),
+ 2 to CommunalContentSize.Responsive(1),
+ 3 to CommunalContentSize.Responsive(1),
)
.inOrder()
- underTest.resizeWidget(2, CommunalContentSize.FULL.span, mapOf(2 to 0, 1 to 1))
+ underTest.resizeWidget(
+ appWidgetId = 2,
+ spanY = 5,
+ widgetIdToRankMap = mapOf(2 to 0, 1 to 1),
+ )
// Widget 2 should have been resized to FULL and moved to the front of the list
assertThat(widgetContent?.map { it.appWidgetId to it.size })
.containsExactly(
- 2 to CommunalContentSize.FULL,
- 1 to CommunalContentSize.HALF,
- 3 to CommunalContentSize.HALF,
+ 2 to CommunalContentSize.Responsive(5),
+ 1 to CommunalContentSize.Responsive(1),
+ 3 to CommunalContentSize.Responsive(1),
)
.inOrder()
}
@@ -1191,9 +1326,15 @@ class CommunalInteractorTest : SysuiTestCase() {
)
}
- private companion object {
- val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
- val USER_INFO_WORK =
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ }
+
+ private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ private val USER_INFO_WORK =
UserInfo(
10,
"work",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 755c4ebf5016..ca7e2032be93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,8 +85,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
setUpState(
isShadeTouchable = false,
@@ -103,8 +102,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
}
@Test
@@ -122,7 +120,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
setUpState(
isShadeTouchable = false,
@@ -140,7 +138,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
}
@Test
@@ -158,9 +156,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
setUpState(
isShadeTouchable = false,
@@ -174,9 +170,7 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
}
private fun TestScope.setUpState(
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 3eba8ff4b198..763ea392deb9 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
@@ -18,12 +18,14 @@ package com.android.systemui.communal.view.viewmodel
import android.content.ComponentName
import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
@@ -248,7 +250,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
testScope.runTest {
// Widgets available.
@@ -280,11 +284,13 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java)
assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
- assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF)
- assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF)
+ assertThat(timer?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+ assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
}
+ /** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
testScope.runTest {
// Widgets available.
@@ -324,9 +330,9 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
// One full-sized timer and a half-sized timer and half-sized UMO.
- assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF)
- assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF)
- assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL)
+ assertThat(timer1?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+ assertThat(timer2?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+ assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.FULL)
}
@Test
@@ -891,7 +897,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ .andSceneContainer()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt
new file mode 100644
index 000000000000..e1344caa8677
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.display.domain.interactor
+
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import android.view.layoutInflater
+import android.view.windowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DisplayWindowPropertiesInteractorImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val repo = kosmos.fakeDisplayWindowPropertiesRepository
+
+ private val underTest = kosmos.displayWindowPropertiesInteractor
+
+ @Test
+ fun getForStatusBar_returnsPropertiesWithCorrectWindowType() {
+ val displayId = 123
+ val statusBarWindowProperties = createDisplayWindowProperties(displayId, TYPE_STATUS_BAR)
+ val navBarWindowProperties = createDisplayWindowProperties(displayId, TYPE_NAVIGATION_BAR)
+ repo.insert(statusBarWindowProperties)
+ repo.insert(navBarWindowProperties)
+
+ assertThat(underTest.getForStatusBar(displayId)).isEqualTo(statusBarWindowProperties)
+ }
+
+ private fun createDisplayWindowProperties(displayId: Int, windowType: Int) =
+ DisplayWindowProperties(
+ displayId,
+ windowType,
+ context,
+ kosmos.windowManager,
+ kosmos.layoutInflater,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
index 790df03e6401..1e937b46dbcb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
@@ -29,8 +29,11 @@ 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.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.shared.condition.Monitor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import kotlin.test.Test
import org.junit.Before
@@ -48,6 +51,8 @@ import org.mockito.kotlin.whenever
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class DreamOverlayRegistrantTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
private val context = mock<Context>()
private val packageManager = mock<PackageManager>()
@@ -73,6 +78,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() {
monitor,
packageManager,
dreamManager,
+ kosmos.communalSettingsInteractor,
logBuffer,
)
@@ -117,7 +123,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() {
/** Verify overlay registered when enabled in manifest. */
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun testRegisteredWhenEnabledWithManifest() {
serviceInfo.enabled = true
start()
@@ -127,8 +133,10 @@ class DreamOverlayRegistrantTest : SysuiTestCase() {
/** Verify overlay registered for mobile hub with flag. */
@Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun testRegisteredForMobileHub() {
+ kosmos.setCommunalV2ConfigEnabled(true)
+
start()
verify(dreamManager).registerDreamOverlayService(componentName)
@@ -139,7 +147,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() {
* enabled.
*/
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun testDisabledForMobileWithoutMobileHub() {
start()
@@ -154,8 +162,9 @@ class DreamOverlayRegistrantTest : SysuiTestCase() {
/** Ensure service unregistered when component is disabled at runtime. */
@Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun testUnregisteredWhenComponentDisabled() {
+ kosmos.setCommunalV2ConfigEnabled(true)
start()
verify(dreamManager).registerDreamOverlayService(componentName)
clearInvocations(dreamManager)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index a8048793be06..b07097d61b96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -43,7 +43,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -55,13 +55,16 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutEngine
import com.android.systemui.complication.dagger.ComplicationComponent
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent
import com.android.systemui.dreams.dagger.DreamOverlayComponent
import com.android.systemui.dreams.touch.CommunalTouchHandler
import com.android.systemui.flags.andSceneContainer
@@ -119,8 +122,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val mComplicationComponentFactory = mock<ComplicationComponent.Factory>()
private val mComplicationHostViewController = mock<ComplicationHostViewController>()
private val mComplicationVisibilityController = mock<ComplicationLayoutEngine>()
- private val mDreamComplicationComponentFactory =
- mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory>()
+ private val mDreamComplicationComponentFactory = mock<DreamComplicationComponent.Factory>()
private val mHideComplicationTouchHandler = mock<HideComplicationTouchHandler>()
private val mDreamOverlayComponentFactory = mock<DreamOverlayComponent.Factory>()
private val mCommunalTouchHandler = mock<CommunalTouchHandler>()
@@ -160,8 +162,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
private lateinit var mService: DreamOverlayService
private class EnvironmentComponents(
- val dreamsComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+ val dreamsComplicationComponent: DreamComplicationComponent,
val dreamOverlayComponent: DreamOverlayComponent,
val complicationComponent: ComplicationComponent,
val ambientTouchComponent: AmbientTouchComponent,
@@ -186,8 +187,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
}
private fun setupComponentFactories(
- dreamComplicationComponentFactory:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+ dreamComplicationComponentFactory: DreamComplicationComponent.Factory,
dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
complicationComponentFactory: ComplicationComponent.Factory,
ambientTouchComponentFactory: AmbientTouchComponent.Factory,
@@ -208,8 +208,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
whenever(complicationComponent.getVisibilityController())
.thenReturn(mComplicationVisibilityController)
- val dreamComplicationComponent =
- mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+ val dreamComplicationComponent = mock<DreamComplicationComponent>()
whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
.thenReturn(mHideComplicationTouchHandler)
whenever(dreamOverlayComponent.communalTouchHandler).thenReturn(mCommunalTouchHandler)
@@ -265,6 +264,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
mKeyguardUpdateMonitor,
mScrimManager,
mCommunalInteractor,
+ kosmos.communalSettingsInteractor,
kosmos.sceneInteractor,
mSystemDialogsCloser,
mUiEventLogger,
@@ -1286,7 +1286,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
environmentComponents.verifyNoMoreInteractions()
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun testAmbientTouchHandlersRegistration_registerHideComplicationAndCommunal() {
val client = client
@@ -1306,9 +1306,11 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
.containsExactly(mHideComplicationTouchHandler, mCommunalTouchHandler)
}
- @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun testAmbientTouchHandlersRegistration_v2_registerOnlyHideComplication() {
+ kosmos.setCommunalV2ConfigEnabled(true)
+
val client = client
// Inform the overlay service of dream starting.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index 55b87db232e8..d6daa794c45b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -86,8 +86,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
)
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
@@ -105,8 +104,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
)
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
}
@@ -127,7 +125,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
@@ -146,7 +144,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
}
@@ -167,9 +165,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
@@ -184,9 +180,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
assertThat(actions?.get(Swipe.Start)).isNull()
assertThat(actions?.get(Swipe.End)).isNull()
}
@@ -206,8 +200,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
)
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
@@ -225,8 +218,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
)
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
- assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
}
@@ -247,7 +239,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
@@ -266,7 +258,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
}
@@ -287,9 +279,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
@@ -304,9 +294,7 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions).isNotEmpty()
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
- .isEqualTo(
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- )
+ .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
assertThat(actions?.get(Swipe.End)).isNull()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index e288522ec212..248b922bcc77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -20,18 +20,20 @@ import android.app.Dialog
import android.app.Notification
import android.app.NotificationManager
import android.content.applicationContext
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.contextualEducationInteractor
import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor
import com.android.systemui.education.ui.view.ContextualEduUiCoordinator
import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
@@ -56,11 +58,13 @@ 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)
+@RunWith(ParameterizedAndroidJunit4::class)
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
-class ContextualEduUiCoordinatorTest : SysuiTestCase() {
+class ContextualEduUiCoordinatorTest(private val gestureType: GestureType) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val interactor = kosmos.contextualEducationInteractor
@@ -112,23 +116,23 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
@Test
fun showDialogOnNewEdu() =
testScope.runTest {
- triggerEducation(BACK)
+ triggerEducation(gestureType)
verify(dialog).show()
}
@Test
fun showNotificationOn2ndEdu() =
testScope.runTest {
- triggerEducation(BACK)
+ triggerEducation(gestureType)
eduClock.offset(minDurationForNextEdu)
- triggerEducation(BACK)
+ triggerEducation(gestureType)
verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any())
}
@Test
fun dismissDialogAfterTimeout() =
testScope.runTest {
- triggerEducation(BACK)
+ triggerEducation(gestureType)
advanceTimeBy(timeoutMillis + 1)
verify(dialog).dismiss()
}
@@ -142,26 +146,59 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
}
@Test
- fun verifyBackEduToastContent() =
+ fun verifyEduToastContent() =
testScope.runTest {
- triggerEducation(BACK)
- assertThat(toastContent).isEqualTo(context.getString(R.string.back_edu_toast_content))
+ triggerEducation(gestureType)
+
+ val expectedContent =
+ when (gestureType) {
+ BACK -> R.string.back_edu_toast_content
+ HOME -> R.string.home_edu_toast_content
+ OVERVIEW -> R.string.overview_edu_toast_content
+ ALL_APPS -> R.string.all_apps_edu_toast_content
+ }
+
+ assertThat(toastContent).isEqualTo(context.getString(expectedContent))
}
@Test
- fun verifyBackEduNotificationContent() =
+ fun verifyEduNotificationContent() =
testScope.runTest {
val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
- triggerEducation(BACK)
+ triggerEducation(gestureType)
eduClock.offset(minDurationForNextEdu)
- triggerEducation(BACK)
+ triggerEducation(gestureType)
verify(notificationManager)
.notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any())
+
+ val expectedTitle =
+ when (gestureType) {
+ BACK -> R.string.back_edu_notification_title
+ HOME -> R.string.home_edu_notification_title
+ OVERVIEW -> R.string.overview_edu_notification_title
+ ALL_APPS -> R.string.all_apps_edu_notification_title
+ }
+
+ val expectedContent =
+ when (gestureType) {
+ BACK -> R.string.back_edu_notification_content
+ HOME -> R.string.home_edu_notification_content
+ OVERVIEW -> R.string.overview_edu_notification_content
+ ALL_APPS -> R.string.all_apps_edu_notification_content
+ }
+
+ val expectedTutorialClassName =
+ when (gestureType) {
+ OVERVIEW -> TUTORIAL_ACTION
+ else -> KeyboardTouchpadTutorialActivity::class.qualifiedName
+ }
+
verifyNotificationContent(
- R.string.back_edu_notification_title,
- R.string.back_edu_notification_content,
+ expectedTitle,
+ expectedContent,
+ expectedTutorialClassName,
notificationCaptor.value,
)
}
@@ -169,6 +206,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
private fun verifyNotificationContent(
titleResId: Int,
contentResId: Int,
+ expectedTutorialClassName: String?,
notification: Notification,
) {
val expectedContent = context.getString(contentResId)
@@ -177,6 +215,10 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
val actualTitle = notification.getString(Notification.EXTRA_TITLE)
assertThat(actualContent).isEqualTo(expectedContent)
assertThat(actualTitle).isEqualTo(expectedTitle)
+ val actualTutorialClassName =
+ notification.contentIntent.intent.component?.className
+ ?: notification.contentIntent.intent.action
+ assertThat(actualTutorialClassName).isEqualTo(expectedTutorialClassName)
}
private fun Notification.getString(key: String): String =
@@ -188,4 +230,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
}
runCurrent()
}
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getGestureTypes(): List<GestureType> {
+ return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+ }
+
+ private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 76434ee54627..1de38ee3a04e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.inputdevice.tutorial.ui.viewmodel
-import androidx.lifecycle.Lifecycle.Event
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -55,7 +53,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@@ -71,9 +68,6 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
private var tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
private val viewModel by lazy { createViewModel(tutorialScope) }
- // createUnsafe so its methods don't have to be called on Main thread
- private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java))
-
@get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher)
private fun createViewModel(
@@ -88,7 +82,6 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
mock<InputDeviceTutorialLogger>(),
SavedStateHandle(mapOf(INTENT_TUTORIAL_SCOPE_KEY to scope)),
)
- lifecycle.addObserver(viewModel)
return viewModel
}
@@ -279,7 +272,7 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
collectValues(viewModel.screen) // just to initialize viewModel
peripheralsState(touchpadConnected = true)
- lifecycle.handleLifecycleEvent(Event.ON_START)
+ viewModel.onStart(TestLifecycleOwner())
assertGesturesDisabled()
}
@@ -291,8 +284,8 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
collectValues(viewModel.screen)
peripheralsState(touchpadConnected = true)
- lifecycle.handleLifecycleEvent(Event.ON_START)
- lifecycle.handleLifecycleEvent(Event.ON_STOP)
+ viewModel.onStart(TestLifecycleOwner())
+ viewModel.onStop(TestLifecycleOwner())
assertGesturesNotDisabled()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
new file mode 100644
index 000000000000..e659ef274980
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.customInputGesturesRepository
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+class CustomInputGesturesRepositoryTest : SysuiTestCase() {
+
+ private val mockUserContext: Context = mock()
+ private val kosmos = testKosmos().also {
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+ }
+
+ private val inputManager = kosmos.fakeInputManager.inputManager
+ private val testScope = kosmos.testScope
+ private val customInputGesturesRepository = kosmos.customInputGesturesRepository
+
+ @Before
+ fun setup(){
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+ }
+
+ @Test
+ fun customInputGestures_initialValueReturnsDataFromAPI() {
+ testScope.runTest {
+ val customInputGestures = listOf(allAppsInputGestureData)
+ whenever(
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).then { return@then customInputGestures }
+
+ val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
+
+ assertThat(inputGestures).containsExactly(allAppsInputGestureData)
+ }
+ }
+
+ @Test
+ fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() {
+ testScope.runTest {
+ var customInputGestures = listOf<InputGestureData>()
+ whenever(
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).then { return@then customInputGestures }
+ whenever(inputManager.addCustomInputGesture(any())).then { invocation ->
+ val inputGesture = invocation.getArgument<InputGestureData>(0)
+ customInputGestures = customInputGestures + inputGesture
+ return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+
+ val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
+ assertThat(inputGestures).isEmpty()
+
+ customInputGesturesRepository.addCustomInputGesture(allAppsInputGestureData)
+ assertThat(inputGestures).containsExactly(allAppsInputGestureData)
+ }
+ }
+
+ @Test
+ fun retrieveCustomInputGestures_retrievesMostRecentData() {
+ testScope.runTest {
+ var customInputGestures = listOf<InputGestureData>()
+ whenever(
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).then { return@then customInputGestures }
+
+
+ assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty()
+
+ customInputGestures = listOf(allAppsInputGestureData)
+
+ assertThat(customInputGesturesRepository.retrieveCustomInputGestures())
+ .containsExactly(allAppsInputGestureData)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index 0d32b7fb1b3e..d12c04586ac2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -18,6 +18,8 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
@@ -35,14 +37,18 @@ import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestRe
import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutCategory
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
@@ -55,6 +61,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -81,7 +88,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
- fun categories_emitsCorrectlyConvertedShortcutCategories() {
+ fun categories_correctlyConvertsAPIModelsToShortcutHelperModels() {
testScope.runTest {
whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
@@ -187,11 +194,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@Test
fun shortcutBeingCustomized_updatedOnCustomizationRequested() {
testScope.runTest {
- repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
+ repo.onCustomizationRequested(allAppsShortcutAddRequest)
val shortcutBeingCustomized = repo.getShortcutBeingCustomized()
- assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo)
+ assertThat(shortcutBeingCustomized).isEqualTo(allAppsShortcutAddRequest)
}
}
@@ -211,7 +218,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() {
testScope.runTest {
helper.toggle(deviceId = 123)
- repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
+ repo.onCustomizationRequested(allAppsShortcutAddRequest)
val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
@@ -223,7 +230,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() {
testScope.runTest {
helper.toggle(deviceId = 123)
- repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
+ repo.onCustomizationRequested(allAppsShortcutAddRequest)
repo.updateUserKeyCombination(standardKeyCombination)
val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
@@ -242,13 +249,107 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
.thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData))
whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData))
.thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+ helper.toggle(deviceId = 123)
+
+ val result = customizeShortcut(allAppsShortcutDeleteRequest)
+ assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS)
+ }
+ }
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_isUpdatedAfterCustomShortcutIsDeleted() {
+ testScope.runTest {
+ // TODO(b/380445594) refactor tests and move these stubbing to ShortcutHelperTestHelper
+ var customInputGestures = listOf(allAppsInputGestureData)
+ whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+ return@then customInputGestures
+ }
+ whenever(inputManager.removeCustomInputGesture(any())).then {
+ val inputGestureToRemove = it.getArgument<InputGestureData>(0)
+ val containsGesture = customInputGestures.contains(inputGestureToRemove)
+ customInputGestures = customInputGestures - inputGestureToRemove
+ return@then if (containsGesture) CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ else CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
+ }
+ val categories by collectLastValue(repo.categories)
helper.toggle(deviceId = 123)
- repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
- val result = repo.deleteShortcutCurrentlyBeingCustomized()
+ customizeShortcut(customizationRequest = allAppsShortcutDeleteRequest)
+ assertThat(categories).isEmpty()
+ }
+ }
- assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS)
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_isUpdatedAfterCustomShortcutIsAdded() {
+ testScope.runTest {
+ // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+ var customInputGestures = listOf<InputGestureData>()
+ whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+ return@then customInputGestures
+ }
+ whenever(inputManager.addCustomInputGesture(any())).then {
+ val inputGestureToAdd = it.getArgument<InputGestureData>(0)
+ customInputGestures = customInputGestures + inputGestureToAdd
+ return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+ val categories by collectLastValue(repo.categories)
+ helper.toggle(deviceId = 123)
+
+ customizeShortcut(allAppsShortcutAddRequest, standardKeyCombination)
+ assertThat(categories).containsExactly(allAppsShortcutCategory)
+ }
+ }
+
+ private suspend fun customizeShortcut(
+ customizationRequest: ShortcutCustomizationRequestInfo,
+ keyCombination: KeyCombination? = null
+ ): ShortcutCustomizationRequestResult{
+ repo.onCustomizationRequested(customizationRequest)
+ repo.updateUserKeyCombination(keyCombination)
+
+ return when (customizationRequest) {
+ is Add -> {
+ repo.confirmAndSetShortcutCurrentlyBeingCustomized()
+ }
+
+ is Delete -> {
+ repo.deleteShortcutCurrentlyBeingCustomized()
+ }
+
+ else -> {
+ ShortcutCustomizationRequestResult.ERROR_OTHER
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_isUpdatedAfterCustomShortcutsAreReset() {
+ testScope.runTest {
+ // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+ var customInputGestures = listOf(allAppsInputGestureData)
+ whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+ return@then customInputGestures
+ }
+ whenever(
+ inputManager.removeAllCustomInputGestures(
+ /* filter = */ InputGestureData.Filter.KEY
+ )
+ )
+ .then {
+ customInputGestures = emptyList()
+ return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+
+ val categories by collectLastValue(repo.categories)
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+ assertThat(categories).containsExactly(allAppsShortcutCategory)
+ repo.resetAllCustomShortcuts()
+ assertThat(categories).isEmpty()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
index f90ab1fcc75b..3bf59f34db76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt
@@ -40,6 +40,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.recentAppsGroup
import com.android.systemui.keyboard.shortcut.defaultShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
@@ -270,6 +273,31 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() {
assertThat(systemCategory).isEqualTo(expectedCategory)
}
+ @Test
+ fun categories_recentAppsSwitchShortcutsIsMarkedNonCustomizable() {
+ testScope.runTest {
+ helper.setImeShortcuts(emptyList())
+ fakeSystemSource.setGroups(emptyList())
+ fakeMultiTaskingSource.setGroups(recentAppsGroup)
+
+ helper.showFromActivity()
+ val categories by collectLastValue(repo.categories)
+
+ val cycleForwardThroughRecentAppsShortcut =
+ categories?.first { it.type == ShortcutCategoryType.MultiTasking }
+ ?.subCategories?.first { it.label == recentAppsGroup.label }
+ ?.shortcuts?.first { it.label == CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
+
+ val cycleBackThroughRecentAppsShortcut =
+ categories?.first { it.type == ShortcutCategoryType.MultiTasking }
+ ?.subCategories?.first { it.label == recentAppsGroup.label }
+ ?.shortcuts?.first { it.label == CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL }
+
+ assertThat(cycleForwardThroughRecentAppsShortcut?.isCustomizable).isFalse()
+ assertThat(cycleBackThroughRecentAppsShortcut?.isCustomizable).isFalse()
+ }
+ }
+
private fun simpleSubCategory(vararg shortcuts: Shortcut) =
ShortcutSubCategory(simpleGroupLabel, shortcuts.asList())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
new file mode 100644
index 000000000000..f78c692ee4c2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.app.role.RoleManager
+import android.app.role.roleManager
+import android.content.Context
+import android.content.Intent
+import android.content.mockedContext
+import android.content.packageManager
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.RoleData
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.app.ResolverActivity
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.inputGestureDataAdapter
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
+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.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputGestureDataAdapterTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { kosmos ->
+ kosmos.userTracker = FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext })
+ }
+ private val adapter = kosmos.inputGestureDataAdapter
+ private val roleManager = kosmos.roleManager
+ private val packageManager: PackageManager = kosmos.packageManager
+ private val mockUserContext: Context = kosmos.mockedContext
+ private val intent: Intent = mock()
+ private val fakeResolverActivityInfo =
+ ActivityInfo().apply { name = ResolverActivity::class.qualifiedName }
+ private val fakeActivityInfo: ActivityInfo =
+ ActivityInfo().apply {
+ name = FAKE_ACTIVITY_NAME
+ icon = 0x1
+ nonLocalizedLabel = TEST_SHORTCUT_LABEL
+ }
+ private val mockSelectorIntent: Intent = mock()
+
+ @Before
+ fun setup() {
+ whenever(mockUserContext.packageManager).thenReturn(packageManager)
+ whenever(mockUserContext.getSystemService(RoleManager::class.java)).thenReturn(roleManager)
+ whenever(roleManager.isRoleAvailable(TEST_ROLE)).thenReturn(true)
+ whenever(roleManager.getDefaultApplication(TEST_ROLE)).thenReturn(TEST_ROLE_PACKAGE)
+ whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(mock())
+ whenever(packageManager.getLaunchIntentForPackage(TEST_ROLE_PACKAGE)).thenReturn(intent)
+ whenever(intent.selector).thenReturn(mockSelectorIntent)
+ whenever(mockSelectorIntent.categories).thenReturn(setOf(TEST_ACTIVITY_CATEGORY))
+ }
+
+ @Test
+ fun shortcutLabel_whenDefaultAppForCategoryIsNotSet_loadsLabelFromFirstAppMatchingIntent() =
+ kosmos.runTest {
+ setApiToRetrieveResolverActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val label =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label
+
+ assertThat(label).isEqualTo(expectedShortcutLabelForFirstAppMatchingIntent)
+ }
+
+ @Test
+ fun shortcutLabel_whenDefaultAppForCategoryIsSet_loadsLabelOfDefaultApp() {
+ kosmos.runTest {
+ setApiToRetrieveSpecificActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val label =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label
+
+ assertThat(label).isEqualTo(TEST_SHORTCUT_LABEL)
+ }
+ }
+
+ @Test
+ fun shortcutIcon_whenDefaultAppForCategoryIsSet_loadsIconOfDefaultApp() {
+ kosmos.runTest {
+ setApiToRetrieveSpecificActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+ val icon =
+ internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.icon
+
+ assertThat(icon).isNotNull()
+ }
+ }
+
+ @Test
+ fun internalGroupSource_isCorrectlyConvertedWithSimpleInputGestureData() =
+ kosmos.runTest {
+ setApiToRetrieveResolverActivity()
+
+ val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
+ val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
+
+ assertThat(internalGroups).containsExactly(
+ InternalGroupsSource(
+ type = ShortcutCategoryType.AppCategories,
+ groups = listOf(
+ InternalKeyboardShortcutGroup(
+ label = APPLICATION_SHORTCUT_GROUP_LABEL,
+ items = listOf(
+ InternalKeyboardShortcutInfo(
+ label = expectedShortcutLabelForFirstAppMatchingIntent,
+ keycode = KEYCODE_A,
+ modifiers = META_CTRL_ON or META_ALT_ON,
+ isCustomShortcut = true
+ )
+ )
+ )
+ )
+ )
+ )
+ }
+
+ private fun setApiToRetrieveResolverActivity() {
+ whenever(intent.resolveActivityInfo(eq(packageManager), anyInt()))
+ .thenReturn(fakeResolverActivityInfo)
+ }
+
+ private fun setApiToRetrieveSpecificActivity() {
+ whenever(intent.resolveActivityInfo(eq(packageManager), anyInt()))
+ .thenReturn(fakeActivityInfo)
+ }
+
+
+ private fun buildInputGestureDataForAppLaunchShortcut(
+ keyCode: Int = KEYCODE_A,
+ modifiers: Int = META_CTRL_ON or META_ALT_ON,
+ appLaunchData: AppLaunchData = RoleData(TEST_ROLE)
+ ): InputGestureData {
+ return InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keyCode, modifiers))
+ .setAppLaunchData(appLaunchData)
+ .build()
+ }
+
+ private val expectedShortcutLabelForFirstAppMatchingIntent =
+ context.getString(R.string.keyboard_shortcut_group_applications_browser)
+
+ private companion object {
+ private const val TEST_ROLE = "Test Browser Role"
+ private const val TEST_ROLE_PACKAGE = "test.browser.package"
+ private const val APPLICATION_SHORTCUT_GROUP_LABEL = "Applications"
+ private const val FAKE_ACTIVITY_NAME = "Fake activity"
+ private const val TEST_SHORTCUT_LABEL = "Test shortcut label"
+ private const val TEST_ACTIVITY_CATEGORY = Intent.CATEGORY_APP_BROWSER
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
index 715d907b7372..c9f703533e89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
@@ -17,21 +17,28 @@
package com.android.systemui.keyboard.shortcut.data.source
import android.content.res.mainResources
+import android.hardware.input.KeyGlyphMap
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
import android.view.KeyboardShortcutGroup
import android.view.WindowManager.KeyboardShortcutsReceiver
import android.view.mockWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -41,7 +48,8 @@ class InputShortcutsSourceTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val mockWindowManager = kosmos.mockWindowManager
- private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager)
+ private val inputManager = kosmos.fakeInputManager.inputManager
+ private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager, inputManager)
private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null
@@ -88,6 +96,36 @@ class InputShortcutsSourceTest : SysuiTestCase() {
assertThat(groups).hasSize(4)
}
+ @Test
+ @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagEnabled_inputManagerReturnsKeyGlyph_returnsEmojiShortcut() =
+ testScope.runTest {
+ val mockKeyGlyph = mock(KeyGlyphMap::class.java)
+ whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
+ whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
+ wmImeShortcutGroups = null
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val keyCodes = groups[0].items.map { it.keycode }
+ assertThat(keyCodes).contains(KEYCODE_EMOJI_PICKER)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagDisabled_inputManagerReturnsKeyGlyph_returnsNoEmojiShortcut() =
+ testScope.runTest {
+ val mockKeyGlyph = mock(KeyGlyphMap::class.java)
+ whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
+ whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
+ wmImeShortcutGroups = null
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val keyCodes = groups[0].items.map { it.keycode }
+ assertThat(keyCodes).doesNotContain(KEYCODE_EMOJI_PICKER)
+ }
+
companion object {
private const val TEST_DEVICE_ID = 1234
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
new file mode 100644
index 000000000000..60d70897a6fa
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultitaskingShortcutsSourceTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val source = MultitaskingShortcutsSource(kosmos.mainResources, context)
+
+ @Test
+ fun shortcutGroups_doesNotContainCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsNoneIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
+ private const val TEST_DEVICE_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
new file mode 100644
index 000000000000..b9fb3e64777d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.hardware.input.KeyGlyphMap
+import android.hardware.input.KeyGlyphMap.KeyCombination
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_BACK
+import android.view.KeyEvent.KEYCODE_HOME
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SystemShortcutsSourceTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val inputManager = kosmos.fakeInputManager.inputManager
+ private val source = SystemShortcutsSource(kosmos.mainResources, inputManager)
+ private val mockKeyGlyphMap = mock(KeyGlyphMap::class.java)
+ private val functionRowKeyCodes = listOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS)
+
+ @Before
+ fun setUp() {
+ whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
+ whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyphMap)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagEnabled_inputManagerReturnsNoFunctionRowKeys_returnsNoFunctionRowShortcuts() =
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val keyCodes = groups[0].items.map { it.keycode }
+ assertThat(keyCodes).containsNoneIn(functionRowKeyCodes)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagEnabled_inputManagerReturnsFunctionRowKeys_returnsFunctionRowShortcuts() =
+ testScope.runTest {
+ whenever(mockKeyGlyphMap.functionRowKeys)
+ .thenReturn(intArrayOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS))
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val keyCodes = groups[0].items.map { it.keycode }
+ assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagDisabled_inputManagerReturnsNoFunctionRowKeys_returnsDefaultFunctionRowShortcuts() =
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val keyCodes = groups[0].items.map { it.keycode }
+ assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagEnabled_inputManagerReturnsHardwareShortcuts_returnsHardwareShortcuts() =
+ testScope.runTest {
+ whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
+ val hardwareShortcutMap =
+ mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
+ whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+ val hardwareShortcut =
+ Triple(
+ context.getString(R.string.group_system_go_back),
+ KeyEvent.META_META_ON,
+ KeyEvent.KEYCODE_1,
+ )
+ assertThat(shortcuts).contains(hardwareShortcut)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+ fun shortcutGroups_flagDisabled_inputManagerReturnsHardwareShortcuts_returnsNoHardwareShortcuts() =
+ testScope.runTest {
+ val hardwareShortcutMap =
+ mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
+ whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)
+
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+ val hardwareShortcut =
+ Triple(
+ context.getString(R.string.group_system_go_back),
+ KeyEvent.META_META_ON,
+ KeyEvent.KEYCODE_1,
+ )
+ assertThat(shortcuts).doesNotContain(hardwareShortcut)
+ }
+
+ @Test
+ fun shortcutGroups_containsCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsAtLeastElementsIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
+ private const val TEST_DEVICE_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 8466eab2aca6..92c76ff68b60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -35,6 +35,7 @@ import android.view.KeyEvent.META_SHIFT_RIGHT_ON
import android.view.KeyEvent.META_SYM_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
@@ -47,7 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
import com.android.systemui.res.R
object TestShortcuts {
@@ -56,14 +57,14 @@ object TestShortcuts {
KeyboardShortcutInfo(
/* label = */ "Shortcut with repeated label",
/* keycode = */ KeyEvent.KEYCODE_H,
- /* modifiers = */ KeyEvent.META_META_ON,
+ /* modifiers = */ META_META_ON,
)
private val shortcutInfoWithRepeatedLabelAlternate =
KeyboardShortcutInfo(
/* label = */ shortcutInfoWithRepeatedLabel.label,
/* keycode = */ KeyEvent.KEYCODE_L,
- /* modifiers = */ KeyEvent.META_META_ON,
+ /* modifiers = */ META_META_ON,
)
private val shortcutInfoWithRepeatedLabelSecondAlternate =
@@ -73,6 +74,27 @@ object TestShortcuts {
/* modifiers = */ KeyEvent.META_SHIFT_ON,
)
+ private val ShortcutsWithDiffSizeOfKeys =
+ KeyboardShortcutInfo(
+ /* label = */ "Shortcuts with diff size of keys",
+ /* keycode = */ KeyEvent.KEYCODE_HOME,
+ /* modifiers = */ 0,
+ )
+
+ private val ShortcutsWithDiffSizeOfKeys2 =
+ KeyboardShortcutInfo(
+ /* label = */ ShortcutsWithDiffSizeOfKeys.label,
+ /* keycode = */ KeyEvent.KEYCODE_1,
+ /* modifiers = */ META_META_ON,
+ )
+
+ private val ShortcutsWithDiffSizeOfKeys3 =
+ KeyboardShortcutInfo(
+ /* label = */ ShortcutsWithDiffSizeOfKeys.label,
+ /* keycode = */ KeyEvent.KEYCODE_2,
+ /* modifiers = */ META_META_ON or META_FUNCTION_ON,
+ )
+
private val shortcutWithGroupedRepeatedLabel =
shortcut(shortcutInfoWithRepeatedLabel.label!!.toString()) {
command {
@@ -87,6 +109,10 @@ object TestShortcuts {
key("Shift")
key("M")
}
+ contentDescription {
+ "${shortcutInfoWithRepeatedLabel.label}, " +
+ "Press key Meta plus H, or Meta plus L, or Shift plus M"
+ }
}
private val goHomeShortcutInfo =
@@ -100,15 +126,51 @@ object TestShortcuts {
KeyboardShortcutInfo(
/* label = */ "Standard shortcut 1",
/* keycode = */ KeyEvent.KEYCODE_N,
- /* modifiers = */ KeyEvent.META_META_ON,
+ /* modifiers = */ META_META_ON,
+ )
+
+ const val CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL = "Cycle forward through recent apps"
+ const val CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL = "Cycle backward through recent apps"
+
+ private val recentAppsCycleForwardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ CYCLE_FORWARD_THROUGH_RECENT_APPS_SHORTCUT_LABEL,
+ /* keycode = */ KeyEvent.KEYCODE_N,
+ /* modifiers = */ META_META_ON,
)
+ private val recentAppsCycleBackShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ CYCLE_BACK_THROUGH_RECENT_APPS_SHORTCUT_LABEL,
+ /* keycode = */ KeyEvent.KEYCODE_N,
+ /* modifiers = */ META_META_ON,
+ )
+
+ private val recentAppsCycleForwardShortcut =
+ shortcut(recentAppsCycleForwardShortcutInfo.label!!.toString()) {
+ command {
+ key(R.drawable.ic_ksh_key_meta)
+ key("N")
+ }
+ isCustomizable = false
+ }
+
+ private val recentAppsCycleBackShortcut =
+ shortcut(recentAppsCycleBackShortcutInfo.label!!.toString()) {
+ command {
+ key(R.drawable.ic_ksh_key_meta)
+ key("N")
+ }
+ isCustomizable = false
+ }
+
private val standardShortcut1 =
shortcut(standardShortcutInfo1.label!!.toString()) {
command {
key(R.drawable.ic_ksh_key_meta)
key("N")
}
+ contentDescription { "${standardShortcutInfo1.label}, Press key Meta plus N" }
}
private val customGoHomeShortcut =
@@ -119,6 +181,7 @@ object TestShortcuts {
key("A")
isCustom(true)
}
+ contentDescription { "Go to home screen, Press key Ctrl plus Alt plus A" }
}
private val standardShortcutInfo2 =
@@ -135,6 +198,7 @@ object TestShortcuts {
key("Shift")
key("Z")
}
+ contentDescription { "${standardShortcutInfo2.label}, Press key Alt plus Shift plus Z" }
}
private val standardShortcutInfo3 =
@@ -150,13 +214,14 @@ object TestShortcuts {
key("Ctrl")
key("J")
}
+ contentDescription { "${standardShortcutInfo3.label}, Press key Ctrl plus J" }
}
private val shortcutInfoWithUnsupportedModifiers =
KeyboardShortcutInfo(
/* label = */ "Shortcut with unsupported modifiers",
/* keycode = */ KeyEvent.KEYCODE_A,
- /* modifiers = */ KeyEvent.META_META_ON or KeyEvent.KEYCODE_SPACE,
+ /* modifiers = */ META_META_ON or KeyEvent.KEYCODE_SPACE,
)
private val groupWithRepeatedShortcutLabels =
@@ -205,6 +270,7 @@ object TestShortcuts {
key("Ctrl")
key("Space")
}
+ contentDescription { "Switch to next language, Press key Ctrl plus Space" }
}
private val switchToPreviousLanguageShortcut =
@@ -214,6 +280,9 @@ object TestShortcuts {
key("Shift")
key("Space")
}
+ contentDescription {
+ "Switch to previous language, Press key Ctrl plus Shift plus Space"
+ }
}
private val subCategoryForInputLanguageSwitchShortcuts =
@@ -228,6 +297,12 @@ object TestShortcuts {
listOf(standardShortcut3),
)
+ val recentAppsGroup =
+ KeyboardShortcutGroup(
+ "Recent apps",
+ listOf(recentAppsCycleForwardShortcutInfo, recentAppsCycleBackShortcutInfo),
+ )
+
private val standardGroup1 =
KeyboardShortcutGroup(
"Standard group 1",
@@ -246,6 +321,12 @@ object TestShortcuts {
private val standardSystemAppSubcategoryWithCustomHomeShortcut =
ShortcutSubCategory("System controls", listOf(customGoHomeShortcut))
+ private val recentAppsSubCategory =
+ ShortcutSubCategory(
+ recentAppsGroup.label!!.toString(),
+ listOf(recentAppsCycleForwardShortcut, recentAppsCycleBackShortcut),
+ )
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
@@ -292,6 +373,10 @@ object TestShortcuts {
key("A")
isCustom(true)
}
+ contentDescription {
+ "Go to home screen, Press key Ctrl plus Alt plus B, " +
+ "or Ctrl plus Alt plus A"
+ }
}
),
)
@@ -337,6 +422,9 @@ object TestShortcuts {
),
)
+ val multitaskingCategoryWithRecentAppsGroup =
+ ShortcutCategory(type = MultiTasking, subCategories = listOf(recentAppsSubCategory))
+
val multitaskingGroups = listOf(standardGroup2, standardGroup1)
val multitaskingCategory =
ShortcutCategory(
@@ -364,6 +452,16 @@ object TestShortcuts {
groupWithSupportedAndUnsupportedModifierShortcut,
)
+ val groupWithDifferentSizeOfShortcutKeys =
+ KeyboardShortcutGroup(
+ "Group with different size of shortcut keys",
+ listOf(
+ ShortcutsWithDiffSizeOfKeys3,
+ ShortcutsWithDiffSizeOfKeys,
+ ShortcutsWithDiffSizeOfKeys2,
+ ),
+ )
+
val subCategoriesWithUnsupportedModifiersRemoved =
listOf(subCategoryWithStandardShortcut, subCategoryWithUnsupportedShortcutsRemoved)
@@ -391,6 +489,7 @@ object TestShortcuts {
category: ShortcutCategoryType,
subcategoryLabel: String,
shortcutLabel: String,
+ includeInCustomization: Boolean = true,
): ShortcutCategory {
return ShortcutCategory(
type = category,
@@ -398,13 +497,13 @@ object TestShortcuts {
listOf(
ShortcutSubCategory(
label = subcategoryLabel,
- shortcuts = listOf(simpleShortcut(shortcutLabel)),
+ shortcuts = listOf(simpleShortcut(shortcutLabel, includeInCustomization)),
)
),
)
}
- private fun simpleShortcut(label: String) =
+ private fun simpleShortcut(label: String, includeInCustomization: Boolean = true) =
Shortcut(
label = label,
commands =
@@ -419,6 +518,7 @@ object TestShortcuts {
),
)
),
+ isCustomizable = includeInCustomization,
)
val customizableInputGestureWithUnknownKeyGestureType =
@@ -467,24 +567,15 @@ object TestShortcuts {
),
simpleShortcutCategory(System, "System controls", "Show shortcuts"),
simpleShortcutCategory(System, "System controls", "View recent apps"),
- simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
- simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
- simpleShortcutCategory(AppCategories, "Applications", "Browser"),
- simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
- simpleShortcutCategory(AppCategories, "Applications", "Email"),
- simpleShortcutCategory(AppCategories, "Applications", "Maps"),
- simpleShortcutCategory(AppCategories, "Applications", "SMS"),
- simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
- )
- val customInputGestureTypeHome =
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ )
+ val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME)
val allCustomizableInputGesturesWithSimpleShortcutCombinations =
listOf(
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME),
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
),
@@ -517,39 +608,18 @@ object TestShortcuts {
),
simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
- ),
- simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
),
)
- val standardAddCustomShortcutRequestInfo =
+ val allAppsShortcutAddRequest =
ShortcutCustomizationRequestInfo.Add(
label = "Open apps list",
categoryType = System,
subCategoryLabel = "System controls",
)
- val standardDeleteCustomShortcutRequestInfo =
+ val allAppsShortcutDeleteRequest =
ShortcutCustomizationRequestInfo.Delete(
label = "Open apps list",
categoryType = System,
@@ -594,8 +664,19 @@ object TestShortcuts {
)
.build()
- val expectedStandardDeleteShortcutUiState =
- ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
+ val allAppsShortcutCategory =
+ shortcutCategory(System) {
+ subCategory("System controls") {
+ shortcut("Open apps list") {
+ command {
+ isCustom(true)
+ key(ShortcutHelperKeys.metaModifierIconResId)
+ key("Shift")
+ key("A")
+ }
+ }
+ }
+ }
val keyDownEventWithoutActionKeyPressed =
androidx.compose.ui.input.key.KeyEvent(
@@ -636,15 +717,7 @@ object TestShortcuts {
val standardAddShortcutRequest =
ShortcutCustomizationRequestInfo.Add(
label = "Standard shortcut",
- categoryType = ShortcutCategoryType.System,
+ categoryType = System,
subCategoryLabel = "Standard subcategory",
)
-
- val expectedStandardAddShortcutUiState =
- ShortcutCustomizationUiState.AddShortcutDialog(
- shortcutLabel = "Standard shortcut",
- defaultCustomShortcutModifierKey =
- ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
- isDialogShowing = false,
- )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index f7c77017e239..8f0bc640f0eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -333,6 +333,56 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun categories_addsSameContentDescriptionForShortcutsOfSameType() {
+ testScope.runTest {
+ setCustomInputGestures(listOf(customInputGestureTypeHome))
+ systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo)
+ helper.showFromActivity()
+
+ val categories by collectLastValue(interactor.shortcutCategories)
+ val contentDescriptions =
+ categories?.flatMap {
+ it.subCategories.flatMap { it.shortcuts.map { it.contentDescription } }
+ }
+
+ assertThat(contentDescriptions)
+ .containsExactly(
+ "Go to home screen, Press key Ctrl plus Alt plus B, or Ctrl plus Alt plus A",
+ "Standard shortcut 3, Press key Ctrl plus J",
+ "Standard shortcut 2, Press key Alt plus Shift plus Z",
+ "Standard shortcut 1, Press key Meta plus N",
+ "Standard shortcut 1, Press key Meta plus N",
+ "Standard shortcut 2, Press key Alt plus Shift plus Z",
+ "Standard shortcut 3, Press key Ctrl plus J",
+ "Switch to next language, Press key Ctrl plus Space",
+ "Switch to previous language, Press key Ctrl plus Shift plus Space",
+ "Standard shortcut 1, Press key Meta plus N",
+ "Standard shortcut 2, Press key Alt plus Shift plus Z",
+ "Standard shortcut 3, Press key Ctrl plus J",
+ "Standard shortcut 3, Press key Ctrl plus J",
+ "Standard shortcut 2, Press key Alt plus Shift plus Z",
+ "Standard shortcut 1, Press key Meta plus N",
+ "Standard shortcut 2, Press key Alt plus Shift plus Z",
+ "Standard shortcut 1, Press key Meta plus N",
+ )
+ }
+ }
+
+ @Test
+ fun categories_showShortcutsInAscendingOrderOfKeySize() =
+ testScope.runTest {
+ systemShortcutsSource.setGroups(TestShortcuts.groupWithDifferentSizeOfShortcutKeys)
+ val categories by collectLastValue(interactor.shortcutCategories)
+
+ helper.showFromActivity()
+
+ val systemCategoryShortcuts = categories?.get(0)?.subCategories?.get(0)?.shortcuts
+ val shortcutKeyCount =
+ systemCategoryShortcuts?.flatMap { it.commands }?.map { it.keys.size }
+ assertThat(shortcutKeyCount).containsExactly(1, 2, 3).inOrder()
+ }
+
private fun setCustomInputGestures(customInputGestures: List<InputGestureData>) {
whenever(fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(customInputGestures)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index d0ce34c2d68d..755c218f6789 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -28,25 +28,26 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardAddShortcutUiState
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardDeleteShortcutUiState
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,8 +63,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
private val mockUserContext: Context = mock()
private val kosmos =
- Kosmos().also {
- it.testCase = this
+ testKosmos().also {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
private val testScope = kosmos.testScope
@@ -92,43 +92,33 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
- }
- }
-
- @Test
- fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() {
- testScope.runTest {
- viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
- val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-
- assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState)
+ assertThat(uiState).isEqualTo(
+ AddShortcutDialog(
+ shortcutLabel = "Standard shortcut",
+ defaultCustomShortcutModifierKey =
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ )
+ )
}
}
@Test
- fun uiState_consumedOnAddDialogShown() {
+ fun uiState_correctlyUpdatedWhenResetShortcutCustomizationIsRequested() {
testScope.runTest {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- viewModel.onDialogShown()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
- .isTrue()
+ assertThat(uiState).isEqualTo(ResetShortcutDialog)
}
}
@Test
- fun uiState_consumedOnDeleteDialogShown() {
+ fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() {
testScope.runTest {
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
- viewModel.onDialogShown()
- assertThat(
- (uiState as ShortcutCustomizationUiState.DeleteShortcutDialog).isDialogShowing
- )
- .isTrue()
+ assertThat(uiState).isEqualTo(DeleteShortcutDialog)
}
}
@@ -137,7 +127,6 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- viewModel.onDialogShown()
viewModel.onDialogDismissed()
assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
}
@@ -148,7 +137,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
@@ -170,10 +159,9 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_errorMessage_isEmptyByDefault() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo)
- viewModel.onDialogShown()
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEmpty()
}
}
@@ -187,7 +175,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -205,7 +193,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -223,7 +211,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
}
}
@@ -244,6 +232,18 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
+ fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList())
+
+ openResetShortcutDialogAndResetAllCustomShortcuts()
+
+ assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+ }
+ }
+
+ @Test
fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
@@ -272,7 +272,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
}
}
@@ -286,20 +286,19 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
// Close the dialog and show it again
viewModel.onDialogDismissed()
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
private suspend fun openAddShortcutDialogAndSetShortcut() {
- viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo)
- viewModel.onDialogShown()
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
@@ -308,9 +307,14 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
private suspend fun openDeleteShortcutDialogAndDeleteShortcut() {
- viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
- viewModel.onDialogShown()
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
viewModel.deleteShortcutCurrentlyBeingCustomized()
}
+
+ private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+ viewModel.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index feae901114e5..78fce276a5f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -18,6 +18,13 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel
import android.app.role.RoleManager
import android.app.role.mockRoleManager
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.hardware.input.InputGestureData
+import android.hardware.input.fakeInputManager
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
@@ -31,6 +38,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
@@ -56,7 +64,6 @@ import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.settings.userTracker
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -64,6 +71,10 @@ 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.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -73,6 +84,9 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
+ private val mockPackageManager: PackageManager = mock()
+ private val mockUserContext: Context = mock()
+ private val mockApplicationInfo: ApplicationInfo = mock()
private val kosmos =
Kosmos().also {
@@ -83,7 +97,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
- it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { context })
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
private val testScope = kosmos.testScope
@@ -91,13 +105,20 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
private val sysUiState = kosmos.sysUiState
private val fakeUserTracker = kosmos.fakeUserTracker
private val mockRoleManager = kosmos.mockRoleManager
+ private val inputManager = kosmos.fakeInputManager.inputManager
private val viewModel = kosmos.shortcutHelperViewModel
+
@Before
fun setUp() {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+ whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))).thenReturn(mockApplicationInfo)
+ whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)).thenReturn("Current App")
+ whenever(mockPackageManager.getApplicationIcon(anyString())).thenThrow(NameNotFoundException())
+ whenever(mockUserContext.packageManager).thenReturn(mockPackageManager)
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
}
@Test
@@ -259,11 +280,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
testScope.runTest {
whenever(
- mockRoleManager.getRoleHoldersAsUser(
- RoleManager.ROLE_HOME,
- fakeUserTracker.userHandle,
- )
+ mockRoleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME,
+ fakeUserTracker.userHandle,
)
+ )
.thenReturn(listOf(TestShortcuts.currentAppPackageName))
val uiState by collectLastValue(viewModel.shortcutsUiState)
@@ -299,23 +320,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
label = "System",
iconSource = IconSource(imageVector = Icons.Default.Tv),
shortcutCategory =
- ShortcutCategory(
- System,
- subCategoryWithShortcutLabels("first Foo shortcut1"),
- subCategoryWithShortcutLabels(
- "second foO shortcut2",
- subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
- ),
+ ShortcutCategory(
+ System,
+ subCategoryWithShortcutLabels("first Foo shortcut1"),
+ subCategoryWithShortcutLabels(
+ "second foO shortcut2",
+ subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
),
+ ),
),
ShortcutCategoryUi(
label = "Multitasking",
iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
shortcutCategory =
- ShortcutCategory(
- MultiTasking,
- subCategoryWithShortcutLabels("third FoO shortcut1"),
- ),
+ ShortcutCategory(
+ MultiTasking,
+ subCategoryWithShortcutLabels("third FoO shortcut1"),
+ ),
),
)
}
@@ -387,6 +408,31 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
}
+ @Test
+ fun shortcutsUiState_shouldShowResetButton_isFalseWhenThereAreNoCustomShortcuts() =
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.shouldShowResetButton).isFalse()
+ }
+
+ @Test
+ fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() =
+ testScope.runTest {
+ whenever(
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).thenReturn(listOf(allAppsInputGestureData))
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.shouldShowResetButton).isTrue()
+ }
+
private fun groupWithShortcutLabels(
vararg shortcutLabels: String,
groupLabel: String = FIRST_SIMPLE_GROUP_LABEL,
@@ -413,6 +459,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
key("Ctrl")
key("A")
}
+ contentDescription { "$label, Press key Ctrl plus A" }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index fcf4662be145..50ac26196978 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -405,7 +405,8 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
testScope.runTest {
val lockScreenState by collectLastValue(underTest.lockScreenState)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ val manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE
+ zenModeRepository.addMode(manualDnd)
runCurrent()
assertThat(lockScreenState)
@@ -419,8 +420,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
)
)
- zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ zenModeRepository.activateMode(manualDnd)
runCurrent()
assertThat(lockScreenState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
index 77c615cce287..789b10b7830c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
@@ -25,7 +25,8 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalEnabled
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
@@ -38,7 +39,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,7 +48,7 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON)
+@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, Flags.FLAG_GLANCEABLE_HUB_V2)
@RunWith(ParameterizedAndroidJunit4::class)
class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -69,6 +69,7 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy
context = context,
communalInteractor = kosmos.communalInteractor,
communalSceneRepository = kosmos.communalSceneRepository,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = kosmos.sceneInteractor,
)
}
@@ -76,28 +77,30 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy
@Test
fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() =
testScope.runTest {
- kosmos.setCommunalEnabled(true)
+ kosmos.setCommunalV2Enabled(true)
runCurrent()
val lockScreenState by collectLastValue(underTest.lockScreenState)
- assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertThat(lockScreenState)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
}
@Test
fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() =
testScope.runTest {
- kosmos.setCommunalEnabled(false)
+ kosmos.setCommunalV2Enabled(false)
val lockScreenState by collectLastValue(underTest.lockScreenState)
runCurrent()
- assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ assertThat(lockScreenState)
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
@Test
fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() =
testScope.runTest {
- kosmos.setCommunalEnabled(true)
+ kosmos.setCommunalV2Enabled(true)
runCurrent()
assertThat(underTest.getPickerScreenState())
@@ -107,7 +110,7 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy
@Test
fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() =
testScope.runTest {
- kosmos.setCommunalEnabled(false)
+ kosmos.setCommunalV2Enabled(false)
runCurrent()
assertThat(
@@ -143,7 +146,8 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
return FlagsParameterization.allCombinationsOf(
- Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON
+ Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON,
+ Flags.FLAG_GLANCEABLE_HUB_V2,
)
.andSceneContainer()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index e1496879a34b..dadcf71a4723 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -35,10 +35,9 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setSceneTransition
@@ -222,28 +221,6 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
}
@Test
- fun resetDismissAction() =
- testScope.runTest {
- kosmos.setSceneTransition(Idle(Scenes.Bouncer))
- var wasOnCancelInvoked = false
- startInteractor()
- keyguardRepository.setDismissAction(
- DismissAction.RunAfterKeyguardGone(
- dismissAction = {},
- onCancelAction = { wasOnCancelInvoked = true },
- message = "message",
- willAnimateOnLockscreen = true,
- )
- )
- assertThat(wasOnCancelInvoked).isFalse()
- kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
- runCurrent()
-
- assertThat(wasOnCancelInvoked).isTrue()
- assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
- }
-
- @Test
fun doNotResetDismissActionOnUnlockedShade() =
testScope.runTest {
kosmos.setSceneTransition(Idle(Scenes.Bouncer))
@@ -272,37 +249,6 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
}
@Test
- fun resetDismissAction_onBouncer_OnAsleep() =
- testScope.runTest {
- kosmos.setSceneTransition(Idle(Scenes.Bouncer))
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- var wasOnCancelInvoked = false
- startInteractor()
-
- keyguardRepository.setDismissAction(
- DismissAction.RunAfterKeyguardGone(
- dismissAction = {},
- onCancelAction = { wasOnCancelInvoked = true },
- message = "message",
- willAnimateOnLockscreen = true,
- )
- )
- assertThat(wasOnCancelInvoked).isFalse()
- kosmos.fakePowerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.TIMEOUT,
- powerButtonLaunchGestureTriggered = false,
- )
- runCurrent()
-
- assertThat(wasOnCancelInvoked).isTrue()
- assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
- }
-
- @Test
fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
testScope.runTest {
val dismissAction by collectLastValue(keyguardRepository.dismissAction)
@@ -410,6 +356,25 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
assertThat(wasCancelActionInvoked).isFalse()
}
+ @Test
+ fun clearDismissAction() =
+ kosmos.runTest {
+ val dismissAction by collectLastValue(fakeKeyguardRepository.dismissAction)
+ fakeKeyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(dismissAction).isNotEqualTo(DismissAction.None)
+
+ underTest.clearDismissAction()
+
+ assertThat(dismissAction).isEqualTo(DismissAction.None)
+ }
+
private fun TestScope.startInteractor() {
testScope.backgroundScope.launchTraced(
"KeyguardDismissActionInteractorTest#startInteractor"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index e07961959f1b..635f80262348 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -995,12 +995,12 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
- sendSteps(sendStep1)
- kosmos.setSceneTransition(Idle(Scenes.Gone))
val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ sendSteps(sendStep1, sendStep2)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
- sendSteps(sendStep2, sendStep3, sendStep4)
+ sendSteps(sendStep3, sendStep4)
assertEquals(listOf<TransitionStep>(), currentStatesMapped)
}
@@ -1134,6 +1134,63 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
)
}
+ @Test
+ @EnableSceneContainer
+ fun transition_filter_on_belongsToInstantReversedTransition_out_of_lockscreen_scene() =
+ testScope.runTest {
+ val currentStatesMapped by
+ collectValues(underTest.transition(Edge.create(LOCKSCREEN, Scenes.Gone)))
+
+ kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
+ val sendStep1 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+ val sendStep2 = TransitionStep(UNDEFINED, LOCKSCREEN, 0.6f, CANCELED)
+ sendSteps(sendStep1, sendStep2)
+ val sendStep3 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ val sendStep4 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
+ sendSteps(sendStep3, sendStep4)
+
+ assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_filter_on_belongsToInstantReversedTransition_into_lockscreen_scene() =
+ testScope.runTest {
+ val currentStatesMapped by
+ collectValues(underTest.transition(Edge.create(Scenes.Gone, LOCKSCREEN)))
+
+ kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED)
+ sendSteps(sendStep1, sendStep2)
+ val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+ val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED)
+ sendSteps(sendStep3, sendStep4)
+
+ assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun transition_filter_on_belongsToInstantReversedTransition_out_of_ls_with_wildcard() =
+ testScope.runTest {
+ val currentStatesMapped by
+ collectValues(underTest.transition(Edge.create(to = LOCKSCREEN)))
+
+ kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
+ val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+ val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED)
+ sendSteps(sendStep1, sendStep2)
+ val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED)
+ val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED)
+ sendSteps(sendStep3, sendStep4)
+
+ assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped)
+ }
+
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 74a0bafda931..df4d5ab90f5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -195,6 +195,7 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
@RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_with_keyguard_shell_transitions() {
underTest.setLockscreenShown(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, false)
underTest.setSurfaceBehindVisibility(true)
verify(keyguardTransitions).startKeyguardTransition(false, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 62cc76345c87..97e67634cd2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -306,8 +306,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
// Top edge is not applicable in dual shade, as well as two-finger swipe.
assertThat(downDestination).isNull()
} else {
- assertThat(downDestination)
- .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
+ assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
assertThat(downDestination?.transitionKey).isNull()
}
@@ -323,7 +322,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
else -> {
assertThat(downFromTopRightDestination)
- .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
+ .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
assertThat(downFromTopRightDestination?.transitionKey).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/kosmos/GeneralKosmosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/kosmos/GeneralKosmosTest.kt
new file mode 100644
index 000000000000..49e553b56554
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/kosmos/GeneralKosmosTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.kosmos
+
+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 kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GeneralKosmosTest : SysuiTestCase() {
+ @Test
+ fun stateCurrentValueMutableStateFlow() = runTest {
+ val source = MutableStateFlow(1)
+ val mapped =
+ source
+ .map { it * 2 }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = source.value * 2,
+ )
+ assertThat(currentValue(mapped)).isEqualTo(2)
+
+ source.value = 3
+ assertThat(currentValue(mapped)).isEqualTo(6)
+ }
+
+ @Test
+ fun stateCurrentValueOnEmittedFlow() = runTest {
+ val source = flow {
+ emit(1)
+ emit(2)
+ }
+ val mapped =
+ source
+ .map { it * 2 }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 2,
+ )
+ assertThat(currentValue(mapped)).isEqualTo(4)
+ }
+
+ @Test
+ fun currentValueIsNull() = runTest {
+ val source = MutableStateFlow<Int?>(null)
+ assertThat(currentValue(source)).isEqualTo(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 01220285e10c..9edd62a8a784 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -99,14 +99,14 @@ class MediaControlViewModelTest : SysuiTestCase() {
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
+ assertThat(underTest.setPlayer(playerModel!!)).isFalse()
}
@Test
@@ -120,7 +120,7 @@ class MediaControlViewModelTest : SysuiTestCase() {
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE)
assertThat(playerModel?.artistName).isEqualTo(ARTIST)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
mediaData = initMediaData(ARTIST_2, TITLE_2)
@@ -129,7 +129,7 @@ class MediaControlViewModelTest : SysuiTestCase() {
assertThat(playerModel).isNotNull()
assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
- assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ assertThat(underTest.setPlayer(playerModel!!)).isTrue()
}
private fun initMediaData(artist: String, title: String): MediaData {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
index 77be8c718b14..6ec38ba171c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.mediarouter.data.repository
-import androidx.test.filters.SmallTest
+import android.media.projection.StopReason
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.Kosmos
@@ -101,7 +102,7 @@ class MediaRouterRepositoryTest : SysuiTestCase() {
origin = CastDevice.CastOrigin.MediaRouter,
)
- underTest.stopCasting(device)
+ underTest.stopCasting(device, StopReason.STOP_UNKNOWN)
assertThat(castController.lastStoppedDevice).isEqualTo(device)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a7329d21..555ba56e087f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -90,6 +90,7 @@ import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavBarHelper;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -116,7 +117,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
@@ -147,6 +148,7 @@ import java.util.concurrent.Executor;
@SmallTest
public class NavigationBarTest extends SysuiTestCase {
private static final int EXTERNAL_DISPLAY_ID = 2;
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private NavigationBar mNavigationBar;
private NavigationBar mExternalDisplayNavigationBar;
@@ -210,10 +212,6 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private LightBarController.Factory mLightBarcontrollerFactory;
@Mock
- private AutoHideController mAutoHideController;
- @Mock
- private AutoHideController.Factory mAutoHideControllerFactory;
- @Mock
private WindowManager mWindowManager;
@Mock
private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@@ -247,6 +245,8 @@ public class NavigationBarTest extends SysuiTestCase {
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
private TaskStackChangeListeners mTaskStackChangeListeners =
TaskStackChangeListeners.getTestInstance();
+ private final AutoHideControllerStore mAutoHideControllerStore =
+ mKosmos.getAutoHideControllerStore();
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@@ -258,7 +258,6 @@ public class NavigationBarTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
- when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
when(mNavigationBarView.getAccessibilityButton()).thenReturn(mAccessibilityButton);
@@ -651,8 +650,7 @@ public class NavigationBarTest extends SysuiTestCase {
mNavBarHelper,
mLightBarController,
mLightBarcontrollerFactory,
- mAutoHideController,
- mAutoHideControllerFactory,
+ mAutoHideControllerStore,
Optional.of(mTelecomManager),
mInputMethodManager,
mDeadZone,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
index b5fc52f7a8d6..87e2fefde989 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -16,11 +16,13 @@
package com.android.systemui.qs.composefragment.viewmodel
+import androidx.compose.runtime.snapshots.Snapshot
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
@@ -36,7 +38,6 @@ import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
-import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
@@ -58,11 +59,14 @@ abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
Dispatchers.setMain(kosmos.testDispatcher)
- }
+ onTeardown { Dispatchers.resetMain() }
- @After
- fun teardown() {
- Dispatchers.resetMain()
+ val globalWriteObserverHandle =
+ Snapshot.registerGlobalWriteObserver {
+ Snapshot.sendApplyNotifications()
+ kosmos.runCurrent()
+ }
+ onTeardown { globalWriteObserverHandle.dispose() }
}
protected inline fun TestScope.testWithinLifecycle(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 921a8a610c37..4457d9b25ade 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -20,7 +20,6 @@ import android.app.StatusBarManager
import android.content.testableContext
import android.graphics.Rect
import android.testing.TestableLooper.RunWithLooper
-import androidx.compose.runtime.snapshots.Snapshot
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -39,6 +38,7 @@ import com.android.systemui.qs.fgsManagerController
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
@@ -186,15 +186,12 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)
underTest.squishinessFraction = 0.3f
- Snapshot.sendApplyNotifications()
assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())
underTest.squishinessFraction = 0f
- Snapshot.sendApplyNotifications()
assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())
underTest.squishinessFraction = 1f
- Snapshot.sendApplyNotifications()
assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
}
}
@@ -328,13 +325,9 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
setMediaState(ACTIVE_MEDIA)
setConfigurationForMediaInRow(mediaInRow = false)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
setConfigurationForMediaInRow(mediaInRow = true)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
}
}
@@ -347,13 +340,9 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
setMediaState(ACTIVE_MEDIA)
setConfigurationForMediaInRow(mediaInRow = false)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
setConfigurationForMediaInRow(mediaInRow = true)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
}
}
@@ -366,13 +355,9 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
setMediaState(ACTIVE_MEDIA)
setCollapsedMediaInLandscape(false)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
setCollapsedMediaInLandscape(true)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
}
}
@@ -401,13 +386,11 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
underTest.squishinessFraction = 0.3f
underTest.shouldUpdateSquishinessOnMedia = true
- Snapshot.sendApplyNotifications()
runCurrent()
assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f)
underTest.shouldUpdateSquishinessOnMedia = false
- Snapshot.sendApplyNotifications()
runCurrent()
assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f)
}
@@ -421,20 +404,15 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
underTest.squishinessFraction = 0.3f
sysuiStatusBarStateController.setState(StatusBarState.SHADE)
- Snapshot.sendApplyNotifications()
runCurrent()
assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f)
sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
runCurrent()
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
runCurrent()
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
}
}
@@ -446,8 +424,6 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
setMediaState(ACTIVE_MEDIA)
setConfigurationForMediaInRow(false)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.disappearParameters)
.isEqualTo(disappearParamsColumn)
@@ -455,14 +431,34 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
.isEqualTo(disappearParamsColumn)
setConfigurationForMediaInRow(true)
- Snapshot.sendApplyNotifications()
- runCurrent()
assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
}
}
+ @Test
+ fun qsVisibleAndAnyShadeVisible() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ underTest.isQsVisible = false
+ fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+ assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse()
+
+ underTest.isQsVisible = true
+ fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+ assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse()
+
+ underTest.isQsVisible = false
+ fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+ assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse()
+
+ underTest.isQsVisible = true
+ fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+ assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isTrue()
+ }
+ }
+
private fun TestScope.setMediaState(state: MediaState) {
with(kosmos) {
val activeMedia = state == ACTIVE_MEDIA
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 09a6c2c7f1f7..1899b7d1f1bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -44,6 +44,7 @@ import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArraySet;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -384,6 +385,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
return mSpec;
}
+ @NonNull
@Override
public State getState() {
return mState;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
new file mode 100644
index 000000000000..f02856c2f5ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.Dialog
+import android.app.StatusBarManager
+import android.content.ComponentName
+import android.content.DialogInterface
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.external.ui.dialog.FakeTileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.external.ui.dialog.fake
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(QSComposeFragment.FLAG_NAME)
+class TileServiceRequestControllerTestComposeOn : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val userId: Int
+ get() = kosmos.currentTilesInteractor.userId.value
+
+ private val mockIcon: Icon
+ get() = mock()
+
+ private val Kosmos.underTest by Kosmos.Fixture { tileServiceRequestController }
+
+ @Before
+ fun setup() {
+ kosmos.fakeInstalledTilesRepository.setInstalledPackagesForUser(
+ userId,
+ setOf(TEST_COMPONENT),
+ )
+ // Start with some tiles, so adding tiles is possible (adding tiles waits until there's
+ // at least one tile, to wait for setup).
+ kosmos.currentTilesInteractor.setTiles(listOf(TileSpec.create("a")))
+ kosmos.runCurrent()
+ }
+
+ @Test
+ fun tileAlreadyAdded_correctResult() =
+ kosmos.runTest {
+ // An existing tile
+ currentTilesInteractor.setTiles(listOf(TILE_SPEC))
+ runCurrent()
+
+ val callback = Callback()
+ runOnMainThreadAndWaitForIdleSync {
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+ assertThat(dialog).isNull()
+ }
+
+ assertThat(callback.lastAccepted).isEqualTo(TILE_ALREADY_ADDED)
+ assertThat(currentTilesInteractor.currentTilesSpecs.count { it == TILE_SPEC })
+ .isEqualTo(1)
+ }
+
+ @Test
+ fun cancelDialog_dismissResult_tileNotAdded() =
+ kosmos.runTest {
+ val callback = Callback()
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )!!
+ }
+
+ runOnMainThreadAndWaitForIdleSync { dialog.cancel() }
+
+ assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+ assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+ }
+
+ @Test
+ fun cancelAndThenDismissSendsOnlyOnce() =
+ kosmos.runTest {
+ // After cancelling, the dialog is dismissed. This tests that only one response
+ // is sent.
+ val callback = Callback()
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )!!
+ }
+
+ runOnMainThreadAndWaitForIdleSync {
+ dialog.cancel()
+ dialog.dismiss()
+ }
+
+ assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+ assertThat(callback.timesCalled).isEqualTo(1)
+ }
+
+ @Test
+ fun showAllUsers_set() =
+ kosmos.runTest {
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ Callback(),
+ )!!
+ }
+ onTeardown { dialog.cancel() }
+
+ assertThat(dialog.isShowForAllUsers).isTrue()
+ }
+
+ @Test
+ fun cancelOnTouchOutside_set() =
+ kosmos.runTest {
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ Callback(),
+ )!!
+ }
+ onTeardown { dialog.cancel() }
+
+ assertThat(dialog.isCancelOnTouchOutside).isTrue()
+ }
+
+ @Test
+ fun positiveAction_tileAdded() =
+ kosmos.runTest {
+ // Not using a real dialog
+ tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+ val callback = Callback()
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+
+ tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+ dialog,
+ DialogInterface.BUTTON_POSITIVE,
+ )
+ runCurrent()
+
+ assertThat(callback.lastAccepted).isEqualTo(ADD_TILE)
+ assertThat(currentTilesInteractor.currentTilesSpecs).hasSize(2)
+ assertThat(currentTilesInteractor.currentTilesSpecs.last()).isEqualTo(TILE_SPEC)
+ }
+
+ @Test
+ fun negativeAction_tileNotAdded() =
+ kosmos.runTest {
+ // Not using a real dialog
+ tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+ val callback = Callback()
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+
+ tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+ dialog,
+ DialogInterface.BUTTON_NEGATIVE,
+ )
+ runCurrent()
+
+ assertThat(callback.lastAccepted).isEqualTo(DONT_ADD_TILE)
+ assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+ }
+
+ companion object {
+ private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+ private val TILE_SPEC = TileSpec.create(TEST_COMPONENT)
+ private const val TEST_APP_NAME = "App"
+ private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
+
+ const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+ const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+ const val TILE_ALREADY_ADDED = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+ const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+ }
+
+ private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
+ var lastAccepted: Int? = null
+ private set
+
+ var timesCalled = 0
+ private set
+
+ override fun accept(t: Int) {
+ lastAccepted = t
+ timesCalled++
+ }
+
+ override fun onTileRequest(r: Int) {
+ accept(r)
+ }
+ }
+}
+
+private val Dialog.isShowForAllUsers: Boolean
+ get() =
+ window!!.attributes.privateFlags and
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS != 0
+
+private val Dialog.isCancelOnTouchOutside: Boolean
+ get() = window!!.shouldCloseOnTouchOutside()
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
new file mode 100644
index 000000000000..369975a95579
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+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.kosmos.Kosmos
+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.qs.QSTile
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+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.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TileRequestDialogViewModelTest : SysuiTestCase() {
+
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val kosmos = testKosmos()
+
+ private val icon: Icon = mock {
+ on {
+ loadDrawableCheckingUriGrant(
+ kosmos.applicationContext,
+ kosmos.iUriGrantsManager,
+ TEST_UID,
+ TEST_PACKAGE,
+ )
+ } doReturn (loadedDrawable)
+ }
+
+ private val tileData = TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_PACKAGE)
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture { tileRequestDialogViewModelFactory.create(applicationContext, tileData) }
+
+ private val baseResultLegacyState =
+ QSTile.State().apply {
+ label = TEST_LABEL
+ state = Tile.STATE_ACTIVE
+ handlesLongClick = false
+ }
+
+ @Test
+ fun uiState_beforeActivation_hasDefaultIcon_andCorrectData() =
+ kosmos.runTest {
+ val expectedState =
+ baseResultLegacyState.apply { icon = defaultIcon }.toUiState(mainResources)
+
+ with(underTest.uiState) {
+ expect.that(label).isEqualTo(TEST_LABEL)
+ expect.that(secondaryLabel).isEmpty()
+ expect.that(state).isEqualTo(expectedState.state)
+ expect.that(handlesLongClick).isFalse()
+ expect.that(handlesSecondaryClick).isFalse()
+ expect.that(icon.get()).isEqualTo(defaultIcon)
+ expect.that(sideDrawable).isNull()
+ expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+ }
+ }
+
+ @Test
+ fun uiState_afterActivation_hasCorrectIcon_andCorrectData() =
+ kosmos.runTest {
+ val expectedState =
+ baseResultLegacyState
+ .apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) }
+ .toUiState(mainResources)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+
+ with(underTest.uiState) {
+ expect.that(label).isEqualTo(TEST_LABEL)
+ expect.that(secondaryLabel).isEmpty()
+ expect.that(state).isEqualTo(expectedState.state)
+ expect.that(handlesLongClick).isFalse()
+ expect.that(handlesSecondaryClick).isFalse()
+ expect.that(icon.get()).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
+ expect.that(sideDrawable).isNull()
+ expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+ }
+ }
+
+ @Test
+ fun uiState_afterActivation_iconNotLoaded_usesDefault() =
+ kosmos.runTest {
+ icon.stub {
+ on {
+ loadDrawableCheckingUriGrant(
+ kosmos.applicationContext,
+ kosmos.iUriGrantsManager,
+ TEST_UID,
+ TEST_PACKAGE,
+ )
+ } doReturn (null)
+ }
+
+ underTest.activateIn(testScope)
+ runCurrent()
+
+ assertThat(underTest.uiState.icon.get()).isEqualTo(defaultIcon)
+ }
+
+ companion object {
+ private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+ private val loadedDrawable = TestStubDrawable("loaded")
+
+ private const val TEST_PACKAGE = "test_pkg"
+ private const val TEST_APP_NAME = "App"
+ private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt
new file mode 100644
index 000000000000..eef195b56188
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.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.systemui.qs.panels.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+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 GridLayoutTypeRepositoryTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+
+ val underTest = kosmos.gridLayoutTypeRepository
+
+ @Test
+ fun defaultType_paginated() =
+ kosmos.runTest {
+ val type by collectLastValue(underTest.defaultLayoutType)
+
+ assertThat(type).isEqualTo(PaginatedGridLayoutType)
+ }
+
+ @Test
+ fun dualShadeType_infinite() =
+ kosmos.runTest {
+ val type by collectLastValue(underTest.dualShadeLayoutType)
+
+ assertThat(type).isEqualTo(InfiniteGridLayoutType)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
new file mode 100644
index 000000000000..b5915386b443
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+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.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+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 GridLayoutTypeInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+
+ val Kosmos.underTest by Kosmos.Fixture { kosmos.gridLayoutTypeInteractor }
+
+ @DisableFlags(DualShade.FLAG_NAME)
+ @Test
+ fun noDualShade_gridAlwaysPaginated() =
+ kosmos.runTest {
+ val type by collectLastValue(underTest.layout)
+
+ fakeShadeRepository.setShadeLayoutWide(false)
+ assertThat(type).isEqualTo(PaginatedGridLayoutType)
+
+ fakeShadeRepository.setShadeLayoutWide(true)
+ assertThat(type).isEqualTo(PaginatedGridLayoutType)
+ }
+
+ @EnableFlags(DualShade.FLAG_NAME)
+ @Test
+ fun dualShade_gridAlwaysInfinite() =
+ kosmos.runTest {
+ val type by collectLastValue(underTest.layout)
+
+ fakeShadeRepository.setShadeLayoutWide(false)
+ assertThat(type).isEqualTo(InfiniteGridLayoutType)
+
+ fakeShadeRepository.setShadeLayoutWide(true)
+ assertThat(type).isEqualTo(InfiniteGridLayoutType)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index a9a527fb8df6..4891c9fb6def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,7 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
index f2bfd729f74a..a8e390c25a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index ee2a1d56937b..411960f5be71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.tileimpl
-import android.animation.AnimatorTestRule
import android.content.Context
import android.service.quicksettings.Tile
import android.view.ContextThemeWrapper
@@ -26,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -77,7 +77,7 @@ class QSIconViewImplTest_311121830 : SysuiTestCase() {
// Set the second state to animate (it shouldn't, because `State.state` is the same) and
// advance time to 2 animations length
iconView.setIcon(secondState, /* allowAnimations= */ true)
- animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+ animatorRule.advanceAnimationDuration(QSIconViewImpl.QS_ANIM_LENGTH * 2)
assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
}
@@ -126,7 +126,7 @@ class QSIconViewImplTest_311121830 : SysuiTestCase() {
// Set the third state to animate and advance time by two times the animation length
// to guarantee that all animations are done
iconView.setIcon(thirdState, /* allowAnimations= */ true)
- animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+ animatorRule.advanceAnimationDuration(QSIconViewImpl.QS_ANIM_LENGTH * 2)
assertThat(iconView.mLastIcon).isEqualTo(thirdState.icon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 7a99aefc98fe..3f4a13414e96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -31,8 +31,10 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
@@ -55,33 +57,22 @@ import org.mockito.kotlin.verify
@SmallTest
class AirplaneModeTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mHost: QSHost
- @Mock
- private lateinit var mMetricsLogger: MetricsLogger
- @Mock
- private lateinit var mStatusBarStateController: StatusBarStateController
- @Mock
- private lateinit var mActivityStarter: ActivityStarter
- @Mock
- private lateinit var mQsLogger: QSLogger
- @Mock
- private lateinit var mBroadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
- @Mock
- private lateinit var mConnectivityManager: ConnectivityManager
- @Mock
- private lateinit var mGlobalSettings: GlobalSettings
- @Mock
- private lateinit var mUserTracker: UserTracker
- @Mock
- private lateinit var mUiEventLogger: QsEventLogger
+ @Mock private lateinit var mHost: QSHost
+ @Mock private lateinit var mMetricsLogger: MetricsLogger
+ @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+ @Mock private lateinit var mActivityStarter: ActivityStarter
+ @Mock private lateinit var mQsLogger: QSLogger
+ @Mock private lateinit var mBroadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+ @Mock private lateinit var mConnectivityManager: ConnectivityManager
+ @Mock private lateinit var mGlobalSettings: GlobalSettings
+ @Mock private lateinit var mUserTracker: UserTracker
+ @Mock private lateinit var mUiEventLogger: QsEventLogger
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
- @Mock
- private lateinit var mClickJob: Job
+ @Mock private lateinit var mClickJob: Job
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -89,20 +80,22 @@ class AirplaneModeTileTest : SysuiTestCase() {
Mockito.`when`(mHost.context).thenReturn(mContext)
Mockito.`when`(mHost.userContext).thenReturn(mContext)
Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
- mTile = AirplaneModeTile(
- mHost,
- mUiEventLogger,
- mTestableLooper.looper,
- Handler(mTestableLooper.looper),
- FalsingManagerFake(),
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQsLogger,
- mBroadcastDispatcher,
- mLazyConnectivityManager,
- mGlobalSettings,
- mUserTracker)
+ mTile =
+ AirplaneModeTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.looper,
+ Handler(mTestableLooper.looper),
+ FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQsLogger,
+ mBroadcastDispatcher,
+ mLazyConnectivityManager,
+ mGlobalSettings,
+ mUserTracker,
+ )
}
@After
@@ -117,8 +110,7 @@ class AirplaneModeTileTest : SysuiTestCase() {
mTile.handleUpdateState(state, 0)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_off))
}
@Test
@@ -127,8 +119,7 @@ class AirplaneModeTileTest : SysuiTestCase() {
mTile.handleUpdateState(state, 1)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_on))
}
@Test
@@ -150,4 +141,12 @@ class AirplaneModeTileTest : SysuiTestCase() {
verify(mConnectivityManager, times(0)).setAirplaneMode(any())
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index d6be31450fc0..ae003497f9e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,8 +31,10 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.settings.FakeSettings
@@ -94,7 +96,7 @@ class BatterySaverTileTest : SysuiTestCase() {
activityStarter,
qsLogger,
batteryController,
- secureSettings
+ secureSettings,
)
tile.initialize()
@@ -150,8 +152,7 @@ class BatterySaverTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_icon_off))
}
@Test
@@ -161,7 +162,14 @@ class BatterySaverTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_icon_on))
+ }
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 093cdf22a64b..31519c584daa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -23,7 +23,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLoggerFake
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,24 +56,15 @@ class CameraToggleTileTest : SysuiTestCase() {
const val CAMERA_TOGGLE_DISABLED: Boolean = true
}
- @Mock
- private lateinit var host: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var privacyController: IndividualSensorPrivacyController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var uiEventLogger: QsEventLoggerFake
- @Mock
- private lateinit var safetyCenterManager: SafetyCenterManager
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var uiEventLogger: QsEventLoggerFake
+ @Mock private lateinit var safetyCenterManager: SafetyCenterManager
private lateinit var testableLooper: TestableLooper
private lateinit var tile: CameraToggleTile
@@ -82,7 +75,8 @@ class CameraToggleTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- tile = CameraToggleTile(
+ tile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -94,7 +88,8 @@ class CameraToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
}
@After
@@ -109,8 +104,7 @@ class CameraToggleTileTest : SysuiTestCase() {
tile.handleUpdateState(state, CAMERA_TOGGLE_ENABLED)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_on))
}
@Test
@@ -119,14 +113,14 @@ class CameraToggleTileTest : SysuiTestCase() {
tile.handleUpdateState(state, CAMERA_TOGGLE_DISABLED)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_off))
}
@Test
fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- val cameraTile = CameraToggleTile(
+ val cameraTile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -138,7 +132,8 @@ class CameraToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
cameraTile.destroy()
testableLooper.processAllMessages()
@@ -147,7 +142,8 @@ class CameraToggleTileTest : SysuiTestCase() {
@Test
fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- val cameraTile = CameraToggleTile(
+ val cameraTile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -159,9 +155,18 @@ class CameraToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
cameraTile.destroy()
testableLooper.processAllMessages()
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 9f12b189d76a..31a627fe0667 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -20,6 +20,7 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -30,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.media.projection.MediaProjectionInfo;
+import android.media.projection.StopReason;
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.TestableLooper;
@@ -336,7 +338,8 @@ public class CastTileTest extends SysuiTestCase {
mCastTile.handleClick(null /* view */);
mTestableLooper.processAllMessages();
- verify(mController, times(1)).stopCasting(same(device));
+ verify(mController, times(1))
+ .stopCasting(same(device), eq(StopReason.STOP_QS_TILE));
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index 1343527e631b..2c796a93613a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -133,7 +134,7 @@ public class ColorInversionTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_icon_off));
}
@Test
@@ -143,6 +144,14 @@ public class ColorInversionTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_icon_on));
+ }
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index 73ae4ee5aa0d..23be9da106d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -29,8 +29,10 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
@@ -84,7 +86,7 @@ class DataSaverTileTest : SysuiTestCase() {
mQsLogger,
dataSaverController,
mDialogTransitionAnimator,
- systemUIDialogFactory
+ systemUIDialogFactory,
)
}
@@ -100,8 +102,7 @@ class DataSaverTileTest : SysuiTestCase() {
tile.handleUpdateState(state, true)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_on))
}
@Test
@@ -110,7 +111,14 @@ class DataSaverTileTest : SysuiTestCase() {
tile.handleUpdateState(state, false)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_off))
+ }
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 940da9967a68..33748b973f1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.classifier.FalsingManagerFake
@@ -45,13 +44,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -67,40 +67,27 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class DeviceControlsTileTest : SysuiTestCase() {
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var controlsComponent: ControlsComponent
- @Mock
- private lateinit var controlsUiController: ControlsUiController
- @Mock
- private lateinit var controlsListingController: ControlsListingController
- @Mock
- private lateinit var controlsController: ControlsController
- @Mock
- private lateinit var serviceInfo: ControlsServiceInfo
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var controlsComponent: ControlsComponent
+ @Mock private lateinit var controlsUiController: ControlsUiController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var serviceInfo: ControlsServiceInfo
+ @Mock private lateinit var uiEventLogger: QsEventLogger
@Captor
private lateinit var listingCallbackCaptor:
- ArgumentCaptor<ControlsListingController.ControlsListingCallback>
- @Captor
- private lateinit var intentCaptor: ArgumentCaptor<Intent>
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+ @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
private lateinit var testableLooper: TestableLooper
private lateinit var tile: DeviceControlsTile
@@ -120,8 +107,11 @@ class DeviceControlsTileTest : SysuiTestCase() {
`when`(qsHost.context).thenReturn(spiedContext)
`when`(controlsComponent.isEnabled()).thenReturn(true)
`when`(controlsController.getPreferredSelection())
- .thenReturn(SelectedItem.StructureItem(
- StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
+ .thenReturn(
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
+ )
+ )
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
setupControlsComponent()
@@ -182,10 +172,11 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testObservingCallback() {
- verify(controlsListingController).observe(
+ verify(controlsListingController)
+ .observe(
any(LifecycleOwner::class.java),
- any(ControlsListingController.ControlsListingCallback::class.java)
- )
+ any(ControlsListingController.ControlsListingCallback::class.java),
+ )
}
@Test
@@ -205,10 +196,8 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateUnavailableIfNoListings() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
listingCallbackCaptor.value.onServicesUpdated(emptyList())
testableLooper.processAllMessages()
@@ -218,10 +207,8 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateUnavailableIfNotEnabled() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.isEnabled()).thenReturn(false)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -232,18 +219,19 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateActiveIfListingsHasControlsFavorited() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
- `when`(controlsController.getPreferredSelection()).thenReturn(
- SelectedItem.StructureItem(StructureInfo(
- ComponentName("pkg", "cls"),
- "structure",
- listOf(ControlInfo("id", "title", "subtitle", 1))
- ))
- )
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(
+ SelectedItem.StructureItem(
+ StructureInfo(
+ ComponentName("pkg", "cls"),
+ "structure",
+ listOf(ControlInfo("id", "title", "subtitle", 1)),
+ )
+ )
+ )
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -253,14 +241,15 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateInactiveIfListingsHasNoControlsFavorited() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsController.getPreferredSelection())
- .thenReturn(SelectedItem.StructureItem(
- StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
+ .thenReturn(
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
+ )
+ )
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -270,13 +259,11 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateActiveIfPreferredIsPanel() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsController.getPreferredSelection())
- .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
+ .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -286,10 +273,8 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testStateInactiveIfLocked() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility())
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
@@ -301,10 +286,8 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun testMoveBetweenStates() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -325,19 +308,20 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Test
fun handleClick_available_shownOverLockscreenWhenLocked() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
- `when`(controlsController.getPreferredSelection()).thenReturn(
- SelectedItem.StructureItem(StructureInfo(
- ComponentName("pkg", "cls"),
- "structure",
- listOf(ControlInfo("id", "title", "subtitle", 1))
- ))
- )
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(
+ SelectedItem.StructureItem(
+ StructureInfo(
+ ComponentName("pkg", "cls"),
+ "structure",
+ listOf(ControlInfo("id", "title", "subtitle", 1)),
+ )
+ )
+ )
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -345,30 +329,33 @@ class DeviceControlsTileTest : SysuiTestCase() {
tile.click(null /* view */)
testableLooper.processAllMessages()
- verify(activityStarter).startActivity(
+ verify(activityStarter)
+ .startActivity(
intentCaptor.capture(),
eq(true) /* dismissShade */,
nullable(ActivityTransitionAnimator.Controller::class.java),
- eq(true) /* showOverLockscreenWhenLocked */)
+ eq(true), /* showOverLockscreenWhenLocked */
+ )
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
@Test
fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
+ verify(controlsListingController)
+ .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
`when`(controlsComponent.getVisibility())
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
- `when`(controlsController.getPreferredSelection()).thenReturn(
- SelectedItem.StructureItem(StructureInfo(
- ComponentName("pkg", "cls"),
- "structure",
- listOf(ControlInfo("id", "title", "subtitle", 1))
- ))
- )
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(
+ SelectedItem.StructureItem(
+ StructureInfo(
+ ComponentName("pkg", "cls"),
+ "structure",
+ listOf(ControlInfo("id", "title", "subtitle", 1)),
+ )
+ )
+ )
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -376,26 +363,19 @@ class DeviceControlsTileTest : SysuiTestCase() {
tile.click(null /* view */)
testableLooper.processAllMessages()
- verify(activityStarter).startActivity(
+ verify(activityStarter)
+ .startActivity(
intentCaptor.capture(),
anyBoolean() /* dismissShade */,
nullable(ActivityTransitionAnimator.Controller::class.java),
- eq(false) /* showOverLockscreenWhenLocked */)
+ eq(false), /* showOverLockscreenWhenLocked */
+ )
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
@Test
fun verifyTileEqualsResourceFromComponent() {
- assertThat(tile.tileLabel)
- .isEqualTo(
- context.getText(
- controlsComponent.getTileTitleId()))
- }
-
- @Test
- fun verifyTileImageEqualsResourceFromComponent() {
- assertThat(tile.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(controlsComponent.getTileImageId()))
+ assertThat(tile.tileLabel).isEqualTo(context.getText(controlsComponent.getTileTitleId()))
}
private fun createTile(): DeviceControlsTile {
@@ -409,11 +389,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
statusBarStateController,
activityStarter,
qsLogger,
- controlsComponent
- ).also {
- it.initialize()
- testableLooper.processAllMessages()
- }
+ controlsComponent,
+ )
+ .also {
+ it.initialize()
+ testableLooper.processAllMessages()
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index f90463e7f589..3cb9091459d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -14,8 +13,11 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.FlashlightController
import com.google.common.truth.Truth
import org.junit.After
@@ -69,7 +71,7 @@ class FlashlightTileTest : SysuiTestCase() {
statusBarStateController,
activityStarter,
qsLogger,
- flashlightController
+ flashlightController,
)
}
@@ -87,8 +89,7 @@ class FlashlightTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_on))
+ Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_on))
}
@Test
@@ -100,7 +101,7 @@ class FlashlightTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
}
@Test
@@ -111,6 +112,14 @@ class FlashlightTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
+ }
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index 9f84e346d54a..f33de4d9144d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -128,7 +128,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
viewModel,
dialogManager,
wifiStateWorker,
- accessPointController
+ accessPointController,
)
underTest.initialize()
@@ -156,10 +156,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
testScope.runTest {
connectivityRepository.defaultConnections.value = DefaultConnectionModel()
wifiRepository.wifiScanResults.value =
- listOf(
- WifiScanEntry(ssid = "ssid 1"),
- WifiScanEntry(ssid = "ssid 2"),
- )
+ listOf(WifiScanEntry(ssid = "ssid 1"), WifiScanEntry(ssid = "ssid 2"))
runCurrent()
looper.processAllMessages()
@@ -204,10 +201,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
testScope.runTest {
airplaneModeRepository.setIsAirplaneMode(true)
connectivityRepository.defaultConnections.value =
- DefaultConnectionModel(
- wifi = Wifi(true),
- isValidated = true,
- )
+ DefaultConnectionModel(wifi = Wifi(true), isValidated = true)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setWifiNetwork(ACTIVE_WIFI)
@@ -222,10 +216,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
fun wifiConnected() =
testScope.runTest {
connectivityRepository.defaultConnections.value =
- DefaultConnectionModel(
- wifi = Wifi(true),
- isValidated = true,
- )
+ DefaultConnectionModel(wifi = Wifi(true), isValidated = true)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setWifiNetwork(ACTIVE_WIFI)
@@ -242,6 +233,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
whenever(wifiStateWorker.isWifiEnabled).thenReturn(true)
underTest.secondaryClick(null)
+ looper.processAllMessages()
verify(wifiStateWorker, times(1)).isWifiEnabled = eq(false)
}
@@ -251,6 +243,7 @@ class InternetTileNewImplTest : SysuiTestCase() {
whenever(wifiStateWorker.isWifiEnabled).thenReturn(false)
underTest.secondaryClick(null)
+ looper.processAllMessages()
verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
}
@@ -258,10 +251,6 @@ class InternetTileNewImplTest : SysuiTestCase() {
companion object {
const val WIFI_SSID = "test ssid"
val ACTIVE_WIFI =
- WifiNetworkModel.Active.of(
- isValidated = true,
- level = 4,
- ssid = WIFI_SSID,
- )
+ WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = WIFI_SSID)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 0cf96047fcc0..b5a64b39f7ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,9 +35,11 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogManager;
@@ -172,7 +174,7 @@ public class InternetTileTest extends SysuiTestCase {
mTile.mSignalCallback.setIsAirplaneMode(state);
mTestableLooper.processAllMessages();
assertThat(mTile.getState().icon).isEqualTo(
- QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+ createExpectedIcon(R.drawable.ic_qs_no_internet_unavailable));
}
@Test
@@ -180,6 +182,7 @@ public class InternetTileTest extends SysuiTestCase {
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(false));
}
@@ -189,7 +192,16 @@ public class InternetTileTest extends SysuiTestCase {
when(mWifiStateWorker.isWifiEnabled()).thenReturn(false);
mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(true));
}
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 0a1455fe12cc..4be189964d6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -30,9 +29,12 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.LocationController
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,27 +54,17 @@ import org.mockito.MockitoAnnotations
@SmallTest
class LocationTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mockContext: Context
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
private val falsingManager = FalsingManagerFake()
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var locationController: LocationController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var panelInteractor: PanelInteractor
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var locationController: LocationController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var panelInteractor: PanelInteractor
+ @Mock private lateinit var uiEventLogger: QsEventLogger
private lateinit var testableLooper: TestableLooper
private lateinit var tile: LocationTile
@@ -83,20 +75,21 @@ class LocationTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
`when`(qsHost.context).thenReturn(mockContext)
- tile = LocationTile(
- qsHost,
- uiEventLogger,
- testableLooper.looper,
- Handler(testableLooper.looper),
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- locationController,
- keyguardStateController,
- panelInteractor,
- )
+ tile =
+ LocationTile(
+ qsHost,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ locationController,
+ keyguardStateController,
+ panelInteractor,
+ )
}
@After
@@ -112,8 +105,7 @@ class LocationTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_off))
}
@Test
@@ -123,8 +115,7 @@ class LocationTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_on))
}
@Test
@@ -140,4 +131,12 @@ class LocationTileTest : SysuiTestCase() {
verify(panelInteractor).openPanels()
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index dbdf3a499f8b..afe9713538de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -23,7 +23,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,36 +56,27 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
const val MICROPHONE_TOGGLE_DISABLED: Boolean = true
}
- @Mock
- private lateinit var host: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var privacyController: IndividualSensorPrivacyController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
- @Mock
- private lateinit var safetyCenterManager: SafetyCenterManager
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var safetyCenterManager: SafetyCenterManager
private lateinit var testableLooper: TestableLooper
private lateinit var tile: MicrophoneToggleTile
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- tile = MicrophoneToggleTile(
+ tile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -95,7 +88,8 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
}
@After
@@ -110,7 +104,7 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
tile.handleUpdateState(state, MICROPHONE_TOGGLE_ENABLED)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_on))
}
@Test
@@ -119,13 +113,14 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
tile.handleUpdateState(state, MICROPHONE_TOGGLE_DISABLED)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_off))
}
@Test
fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- val micTile = MicrophoneToggleTile(
+ val micTile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -137,7 +132,8 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
micTile.destroy()
testableLooper.processAllMessages()
@@ -146,7 +142,8 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
@Test
fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- val micTile = MicrophoneToggleTile(
+ val micTile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -158,9 +155,18 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
micTile.destroy()
testableLooper.processAllMessages()
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 848c8db4b99d..9173ac969324 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -94,11 +94,7 @@ class ModesTileTest : SysuiTestCase() {
private val zenModeRepository = kosmos.zenModeRepository
private val tileDataInteractor =
ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
- private val mapper =
- ModesTileMapper(
- context.resources,
- context.theme,
- )
+ private val mapper = ModesTileMapper(context.resources, context.theme)
private lateinit var userActionInteractor: ModesTileUserActionInteractor
private lateinit var secureSettings: SecureSettings
@@ -127,10 +123,7 @@ class ModesTileTest : SysuiTestCase() {
)
userActionInteractor =
- ModesTileUserActionInteractor(
- inputHandler,
- dialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
underTest =
ModesTile(
@@ -185,7 +178,7 @@ class ModesTileTest : SysuiTestCase() {
ModesTileModel(
isActivated = true,
activeModes = listOf("One", "Two"),
- icon = TestStubDrawable().asIcon()
+ icon = TestStubDrawable().asIcon(),
)
underTest.handleUpdateState(tileState, model)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index f1c589512895..69dab39b279c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,7 +23,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.NightDisplayListenerModule
@@ -32,8 +31,11 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.LocationController
import com.google.common.truth.Truth
import org.junit.After
@@ -42,8 +44,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -72,8 +74,6 @@ class NightDisplayTileTest : SysuiTestCase() {
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: NightDisplayTile
-
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -97,7 +97,7 @@ class NightDisplayTileTest : SysuiTestCase() {
mQsLogger,
mLocationController,
mColorDisplayManager,
- mNightDisplayListenerBuilder
+ mNightDisplayListenerBuilder,
)
}
@@ -115,7 +115,7 @@ class NightDisplayTileTest : SysuiTestCase() {
mTile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_icon_off))
}
@Test
@@ -125,7 +125,14 @@ class NightDisplayTileTest : SysuiTestCase() {
mTile.handleUpdateState(state, /* arg= */ null)
- Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on))
+ Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_icon_on))
+ }
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index f8f82f2c2ed8..682daea2cb1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -112,7 +113,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
assertEquals(state.label, mContext.getString(R.string.qr_code_scanner_title));
assertEquals(state.contentDescription, mContext.getString(R.string.qr_code_scanner_title));
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.ic_qr_code_scanner));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.ic_qr_code_scanner));
}
@Test
@@ -133,4 +134,12 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
assertEquals(state.state, Tile.STATE_INACTIVE);
assertNull(state.secondaryLabel);
}
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 03c1f92aad4c..33951672d05b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -46,6 +46,9 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.service.quickaccesswallet.Flags;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -65,6 +68,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -221,6 +225,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
}
@Test
+ @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
public void testHandleClick_startQuickAccessUiIntent_noCard() {
setUpWalletCard(/* hasCard= */ false);
@@ -234,6 +239,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
}
@Test
+ @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
public void testHandleClick_startQuickAccessUiIntent_hasCard() {
setUpWalletCard(/* hasCard= */ true);
@@ -247,6 +253,34 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
}
@Test
+ @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
+ public void testHandleClick_startCardIntent_noCard() {
+ setUpWalletCard(/* hasCard= */ false);
+
+ mTile.handleClick(/* view= */ null);
+ mTestableLooper.processAllMessages();
+
+ verify(mController).startQuickAccessUiIntent(
+ eq(mActivityStarter),
+ eq(null),
+ /* hasCard= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
+ public void testHandleClick_startCardIntent_hasCard() {
+ setUpWalletCard(/* hasCard= */ true);
+
+ mTile.handleClick(null /* view */);
+ mTestableLooper.processAllMessages();
+
+ verify(mController).startWalletCardPendingIntent(
+ any(),
+ eq(mActivityStarter),
+ eq(null));
+ }
+
+ @Test
public void testHandleUpdateState_updateLabelAndIcon() {
QSTile.State state = new QSTile.State();
@@ -261,7 +295,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() {
when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null);
QSTile.State state = new QSTile.State();
- QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen);
+ QSTile.Icon icon = createExpectedIcon(R.drawable.ic_wallet_lockscreen);
mTile.handleUpdateState(state, null);
@@ -541,5 +575,13 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
}
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 1aff45bf581d..2345128e8c84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -46,6 +46,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R.drawable;
@@ -234,7 +235,7 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on));
+ assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_on));
}
@Test
@@ -245,7 +246,15 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off));
+ assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_off));
+ }
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 41930636cfa3..7fb0eabe96e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -247,7 +248,7 @@ public class RotationLockTileTest extends SysuiTestCase {
mLockTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_off));
}
@Test
@@ -257,7 +258,7 @@ public class RotationLockTileTest extends SysuiTestCase {
mLockTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_on));
}
@@ -281,4 +282,12 @@ public class RotationLockTileTest extends SysuiTestCase {
private void disableCameraBasedRotation() {
when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(false);
}
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 53708fd417e1..a7c7a78dae5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -23,11 +23,13 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Dialog;
+import android.media.projection.StopReason;
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.TestableLooper;
@@ -46,6 +48,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -214,7 +217,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTile.handleClick(null /* view */);
- verify(mController, times(1)).stopRecording();
+ verify(mController, times(1)).stopRecording(eq(StopReason.STOP_QS_TILE));
}
@Test
@@ -266,7 +269,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
}
@Test
@@ -277,7 +280,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
}
@Test
@@ -288,7 +291,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_off));
}
@Test
@@ -314,4 +317,12 @@ public class ScreenRecordTileTest extends SysuiTestCase {
.notifyPermissionRequestDisplayed(mContext.getUserId());
}
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index 8324a7303cff..773e225a6a35 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -33,8 +32,11 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.LocationController
@@ -95,7 +97,7 @@ class UiModeNightTileTest : SysuiTestCase() {
qsLogger,
configurationController,
batteryController,
- locationController
+ locationController,
)
}
@@ -112,8 +114,7 @@ class UiModeNightTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_on))
}
@Test
@@ -124,7 +125,7 @@ class UiModeNightTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_off))
}
private fun setNightModeOn() {
@@ -136,4 +137,12 @@ class UiModeNightTileTest : SysuiTestCase() {
`when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_NO)
configuration.uiMode = Configuration.UI_MODE_NIGHT_NO
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
index 52c476ec92cc..e4a988860a6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -136,4 +137,13 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() {
verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
}
+
+ @Test
+ fun detailsViewModel() =
+ kosmos.testScope.runTest {
+ assertThat(underTest.detailsViewModel.getTitle())
+ .isEqualTo("Internet")
+ assertThat(underTest.detailsViewModel.getSubTitle())
+ .isEqualTo("Tab a network to connect")
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index cd5812710292..88b00468573f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import android.graphics.drawable.TestStubDrawable
@@ -21,16 +23,23 @@ import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,17 +52,17 @@ import org.mockito.kotlin.verify
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val inputHandler = kosmos.qsTileIntentUserInputHandler
private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val zenModeInteractor = kosmos.zenModeInteractor
private val underTest =
- ModesTileUserActionInteractor(
- inputHandler,
- mockDialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
@Test
- fun handleClick_active() = runTest {
+ fun handleClick_active_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
@@ -63,7 +72,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
}
@Test
- fun handleClick_inactive() = runTest {
+ fun handleClick_inactive_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
@@ -73,7 +82,63 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
}
@Test
- fun handleLongClick_active() = runTest {
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_multipleModesActive_deactivatesAll() =
+ testScope.runTest {
+ val activeModes by collectLastValue(zenModeInteractor.activeModes)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Mode 1").setActive(true).build(),
+ TestModeBuilder().setName("Mode 2").setActive(true).build(),
+ )
+ )
+ assertThat(activeModes?.modeNames?.count()).isEqualTo(3)
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(
+ data = modelOf(true, listOf("DND", "Mode 1", "Mode 2"))
+ )
+ )
+
+ assertThat(activeModes?.isAnyActive()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndActive_deactivatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ assertThat(dndMode?.isActive).isTrue()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(true, listOf("DND")))
+ )
+
+ assertThat(dndMode?.isActive).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndInactive_activatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ assertThat(dndMode?.isActive).isFalse()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(false, emptyList()))
+ )
+
+ assertThat(dndMode?.isActive).isTrue()
+ }
+
+ @Test
+ fun handleLongClick_active_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
@@ -82,7 +147,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
}
@Test
- fun handleLongClick_inactive() = runTest {
+ fun handleLongClick_inactive_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
index 0b56d7b64aab..778c73fd8638 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
import android.app.Dialog
+import android.media.projection.StopReason
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -92,7 +93,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {
underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
- verify(recordingController).stopRecording()
+ verify(recordingController).stopRecording(eq(StopReason.STOP_QS_TILE))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index 954215eede0d..2edb9c60711b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -173,6 +173,21 @@ class QSTileViewModelTest : SysuiTestCase() {
.isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER))
}
+ @Test
+ fun tileDetails() =
+ testScope.runTest {
+ assertThat(tileUserActionInteractor.detailsViewModel).isNotNull()
+ assertThat(tileUserActionInteractor.detailsViewModel?.getTitle())
+ .isEqualTo("FakeQSTileUserActionInteractor")
+ assertThat(underTest.detailsViewModel).isNotNull()
+ assertThat(underTest.detailsViewModel?.getTitle())
+ .isEqualTo("FakeQSTileUserActionInteractor")
+
+ tileUserActionInteractor.detailsViewModel = null
+ assertThat(tileUserActionInteractor.detailsViewModel).isNull()
+ assertThat(underTest.detailsViewModel).isNull()
+ }
+
private fun createViewModel(
scope: TestScope,
config: QSTileConfig = tileConfig,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index b5f005cdc706..e56b965d9402 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.currentValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -77,6 +78,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -399,9 +401,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
* Note that this doesn't assert what the current scene is in the UI.
*/
private fun Kosmos.assertCurrentScene(expected: SceneKey) {
- testScope.runCurrent()
assertWithMessage("Current scene mismatch!")
- .that(sceneContainerViewModel.currentScene.value)
+ .that(currentValue(sceneContainerViewModel.currentScene))
.isEqualTo(expected)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index bb2e9417ecef..fc915ca24d89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -104,7 +104,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.QuickSettings))
}
@Test
@@ -116,7 +116,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() {
runCurrent()
assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
- .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a6a1d4a05dc7..50fa9d29659d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -41,6 +41,7 @@ import android.app.ActivityOptions.LaunchCookie;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
+import android.media.projection.StopReason;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -199,16 +200,16 @@ public class RecordingServiceTest extends SysuiTestCase {
public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
doReturn(true).when(mController).isRecording();
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
- verify(mScreenMediaRecorder).end();
+ verify(mScreenMediaRecorder).end(eq(StopReason.STOP_UNKNOWN));
}
@Test
public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
doReturn(true).when(mController).isRecording();
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
assertUpdateState(false);
}
@@ -218,18 +219,18 @@ public class RecordingServiceTest extends SysuiTestCase {
throws IOException {
doReturn(false).when(mController).isRecording();
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
- verify(mScreenMediaRecorder, never()).end();
+ verify(mScreenMediaRecorder, never()).end(StopReason.STOP_UNKNOWN);
}
@Test
public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
throws IOException {
doReturn(true).when(mController).isRecording();
- doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(StopReason.STOP_UNKNOWN);
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
verify(mScreenMediaRecorder).release();
}
@@ -238,7 +239,7 @@ public class RecordingServiceTest extends SysuiTestCase {
public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() {
doReturn(true).when(mController).isRecording();
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
// Processing notification
ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -271,9 +272,9 @@ public class RecordingServiceTest extends SysuiTestCase {
public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
throws IOException {
doReturn(true).when(mController).isRecording();
- doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(anyInt());
- mRecordingService.onStopped();
+ mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
verify(mRecordingService).createErrorSavingNotification(any());
ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -289,9 +290,9 @@ public class RecordingServiceTest extends SysuiTestCase {
public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
throws IOException {
doReturn(true).when(mController).isRecording();
- doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+ doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(anyInt());
- assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+ assertThrows(Throwable.class, () -> mRecordingService.onStopped(StopReason.STOP_UNKNOWN));
verify(mScreenMediaRecorder).release();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 534c12cc0407..3a4c993fd8b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -18,7 +18,10 @@ package com.android.systemui.screenrecord
import android.content.Intent
import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.hardware.display.VirtualDisplayConfig
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.testing.TestableLooper
import android.view.View
import android.widget.Spinner
@@ -42,6 +45,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -224,6 +228,34 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
.notifyProjectionRequestCancelled(TEST_HOST_UID)
}
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.media.projection.flags.Flags
+ .FLAG_MEDIA_PROJECTION_CONNECTED_DISPLAY_NO_VIRTUAL_DEVICE
+ )
+ fun doNotShowVirtualDisplayInDialog() {
+ val displayManager = context.getSystemService(DisplayManager::class.java)!!
+ var virtualDisplay: VirtualDisplay? = null
+ try {
+ virtualDisplay =
+ displayManager.createVirtualDisplay(
+ VirtualDisplayConfig.Builder("virtual display", 1, 1, 160).build()
+ )
+ showDialog()
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val adapter = spinner.adapter
+ val virtualDisplayAvailable =
+ (0 until adapter.count)
+ .mapNotNull { adapter.getItem(it) as? String }
+ .any { it.contains("virtual display", ignoreCase = true) }
+ assertWithMessage("A Virtual Display was shown in the list of display to record")
+ .that(virtualDisplayAvailable)
+ .isFalse()
+ } finally {
+ virtualDisplay?.release()
+ }
+ }
+
private fun showDialog() {
dialog.show()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
index aceea909e595..ade5941d010d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord.data.repository
+import android.media.projection.StopReason
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -31,6 +32,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -126,8 +128,8 @@ class ScreenRecordRepositoryTest : SysuiTestCase() {
@Test
fun stopRecording_invokesController() =
testScope.runTest {
- underTest.stopRecording()
+ underTest.stopRecording(StopReason.STOP_PRIVACY_CHIP)
- verify(recordingController).stopRecording()
+ verify(recordingController).stopRecording(eq(StopReason.STOP_PRIVACY_CHIP))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 612d646bb7d4..53a083f9ceae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -17,13 +17,13 @@
package com.android.systemui.screenshot
import android.content.Intent
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.os.Process.myUserHandle
import android.platform.test.annotations.EnableFlags
import android.testing.TestableContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.screenshot.proxy.SystemUiProxy
+import com.android.systemui.screenshot.proxy.ScreenshotProxy
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -45,7 +45,7 @@ class ActionIntentExecutorTest : SysuiTestCase() {
private val testableContext = TestableContext(mContext)
private val activityManagerWrapper = mock<ActivityManagerWrapper>()
- private val systemUiProxy = mock<SystemUiProxy>()
+ private val screenshotProxy = mock<ScreenshotProxy>()
private val displayTracker = mock<DisplayTracker>()
@@ -55,7 +55,7 @@ class ActionIntentExecutorTest : SysuiTestCase() {
activityManagerWrapper,
testScope,
mainDispatcher,
- systemUiProxy,
+ screenshotProxy,
displayTracker,
)
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 614d51e7ac99..764068ec1bf5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -41,6 +41,7 @@ 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;
@@ -70,6 +71,7 @@ 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;
@@ -111,6 +113,7 @@ 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);
@@ -131,6 +134,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Before
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(
@@ -164,12 +171,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mKosmos.getNotificationShadeWindowModel(),
- mKosmos::getCommunalInteractor) {
- @Override
- protected boolean isDebuggable() {
- return false;
- }
- };
+ mSecureSettings,
+ mKosmos::getCommunalInteractor,
+ mKosmos.getShadeLayoutParams());
mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
mNotificationShadeWindowController.fetchWindowRootView();
@@ -351,6 +355,19 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void setKeyguardShowingWithSecureWindowsDisabled_disablesSecureFlag() {
+ mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 1);
+ mNotificationShadeWindowController.setBouncerShowing(true);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
+ assertThat(
+ (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY)
+ != 0)
+ .isTrue();
+ }
+
+ @Test
public void setKeyguardNotShowing_disablesSecureFlag() {
mNotificationShadeWindowController.setBouncerShowing(false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
new file mode 100644
index 000000000000..ef1ae093bcc9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.shade.display
+
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+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.coroutines.collectValues
+import com.android.systemui.display.data.repository.display
+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 kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val displayRepository = kosmos.displayRepository
+ val underTest = StatusBarTouchShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+ @Test
+ fun displayId_defaultToDefaultDisplay() {
+ assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun onStatusBarTouched_called_updatesDisplayId() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+ underTest.onStatusBarTouched(2)
+
+ assertThat(displayId).isEqualTo(2)
+ }
+
+ @Test
+ fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() =
+ testScope.runTest {
+ val displayIds by collectValues(underTest.displayId)
+ assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
+
+ underTest.onStatusBarTouched(2)
+
+ // Never set, as 2 was not a display according to the repository.
+ assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
+ }
+
+ @Test
+ fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+ underTest.onStatusBarTouched(2)
+
+ assertThat(displayId).isEqualTo(2)
+
+ displayRepository.removeDisplay(2)
+
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+ }
+}
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 982c51b8318c..a8d5c31873de 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
@@ -16,29 +16,25 @@
package com.android.systemui.shade.domain.interactor
-import android.content.Context
+import android.content.mockedContext
import android.content.res.Configuration
-import android.content.res.Resources
+import android.content.res.mockResources
import android.view.Display
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import android.view.mockWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository
-import com.android.systemui.display.shared.model.DisplayWindowProperties
-import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
-import java.util.Optional
+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.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -49,26 +45,18 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@SmallTest
class ShadeDisplaysInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val shadeRootview = mock<WindowRootView>()
- private val positionRepository = FakeShadeDisplayRepository()
- private val shadeContext = mock<Context>()
- private val contextStore = FakeDisplayWindowPropertiesRepository()
- private val testScope = TestScope(UnconfinedTestDispatcher())
- private val shadeWm = mock<WindowManager>()
- private val resources = mock<Resources>()
+ private val shadeRootview = kosmos.mockShadeRootView
+ private val positionRepository = kosmos.fakeShadeDisplaysRepository
+ private val shadeContext = kosmos.mockedContext
+ private val testScope = kosmos.testScope
+ private val shadeWm = kosmos.mockWindowManager
+ private val resources = kosmos.mockResources
private val configuration = mock<Configuration>()
private val display = mock<Display>()
- private val interactor =
- ShadeDisplaysInteractor(
- Optional.of(shadeRootview),
- positionRepository,
- shadeContext,
- shadeWm,
- testScope.backgroundScope,
- testScope.backgroundScope.coroutineContext,
- )
+ private val underTest = kosmos.shadeDisplaysInteractor
@Before
fun setup() {
@@ -80,23 +68,14 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
whenever(shadeContext.displayId).thenReturn(0)
whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm)
whenever(shadeContext.resources).thenReturn(resources)
- contextStore.insert(
- DisplayWindowProperties(
- displayId = 0,
- windowType = TYPE_NOTIFICATION_SHADE,
- context = shadeContext,
- windowManager = shadeWm,
- layoutInflater = mock(),
- )
- )
}
@Test
fun start_shadeInCorrectPosition_notAddedOrRemoved() {
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(0)
- interactor.start()
- testScope.advanceUntilIdle()
+
+ underTest.start()
verifyNoMoreInteractions(shadeWm)
}
@@ -105,7 +84,8 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadeInWrongPosition_changes() {
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(1)
- interactor.start()
+
+ underTest.start()
inOrder(shadeWm).apply {
verify(shadeWm).removeView(eq(shadeRootview))
@@ -117,9 +97,10 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
fun start_shadePositionChanges_removedThenAdded() {
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(0)
- interactor.start()
+ underTest.start()
positionRepository.setDisplayId(1)
+ testScope.advanceUntilIdle()
inOrder(shadeWm).apply {
verify(shadeWm).removeView(eq(shadeRootview))
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 3ebf9f72b5ff..a62d9d5ce62f 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
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.StatusBarIconView
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.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -163,6 +164,28 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val notifKey = "testNotifKey"
+ repo.setOngoingCallState(
+ inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+ )
+ val actualNotifKey =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
+ .notificationKey
+ assertThat(actualNotifKey).isEqualTo(notifKey)
+ }
+
+ @Test
@DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() =
testScope.runTest {
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 7fed47a4653e..f06bab756acc 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,6 +26,8 @@ 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.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -42,7 +45,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
fun notificationChip_startsWithStartingModel() =
kosmos.runTest {
val icon = mock<StatusBarIconView>()
- val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = icon)
+ val startingNotif =
+ activeNotificationModel(key = "notif1", statusBarChipIcon = icon, whenTime = 5432)
val underTest = factory.create(startingNotif)
@@ -50,6 +54,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
+ assertThat(latest!!.whenTime).isEqualTo(5432)
}
@Test
@@ -65,11 +70,16 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val newIconView = mock<StatusBarIconView>()
underTest.setNotification(
- activeNotificationModel(key = "notif1", statusBarChipIcon = newIconView)
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = newIconView,
+ whenTime = 6543,
+ )
)
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
+ assertThat(latest!!.whenTime).isEqualTo(6543)
}
@Test
@@ -104,6 +114,27 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_cdEnabled_missingStatusBarIconChipView_inConstructor_emitsNotNull() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = null,
+ whenTime = 123L,
+ )
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest)
+ .isEqualTo(
+ NotificationChipModel("notif1", statusBarChipIconView = null, whenTime = 123L)
+ )
+ }
+
+ @Test
fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
kosmos.runTest {
val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
@@ -119,6 +150,29 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() =
+ kosmos.runTest {
+ val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+ val underTest = factory.create(startingNotif)
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ underTest.setNotification(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = null, whenTime = 123L)
+ )
+
+ assertThat(latest)
+ .isEqualTo(
+ NotificationChipModel(
+ key = "notif1",
+ statusBarChipIconView = null,
+ whenTime = 123L,
+ )
+ )
+ }
+
+ @Test
fun notificationChip_appIsVisibleOnCreation_emitsNull() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = true
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 702e101d2d39..5a894ca895c3 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
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh
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.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -60,7 +61,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -90,7 +91,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -110,7 +111,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -133,17 +134,17 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = false,
+ promotedContent = null,
),
)
)
@@ -170,7 +171,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -183,7 +184,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -196,7 +197,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = thirdIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -216,7 +217,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -228,7 +229,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- isPromoted = false,
+ promotedContent = null,
)
)
)
@@ -239,7 +240,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -260,7 +261,8 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif|uid1",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("notif|uid1").build(),
)
)
)
@@ -274,7 +276,8 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif|uid2",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("notif|uid2").build(),
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 16376c5b3850..8a4ddceb0d3a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -29,9 +29,11 @@ import com.android.systemui.statusbar.StatusBarIconView
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.ui.model.OngoingActivityChipModel
+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.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -75,7 +77,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -94,18 +96,42 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
}
@Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val notifKey = "notif"
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = notifKey,
+ statusBarChipIcon = null,
+ promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ val chip = latest!![0]
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ assertThat(chip.icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
+ }
+
+ @Test
fun chips_onlyForPromotedNotifs() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -117,17 +143,17 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = false,
+ promotedContent = null,
),
)
)
@@ -138,6 +164,41 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val firstKey = "notif1"
+ val secondKey = "notif2"
+ val thirdKey = "notif3"
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = firstKey,
+ statusBarChipIcon = null,
+ promotedContent = PromotedNotificationContentModel.Builder(firstKey).build(),
+ ),
+ activeNotificationModel(
+ key = secondKey,
+ statusBarChipIcon = null,
+ promotedContent =
+ PromotedNotificationContentModel.Builder(secondKey).build(),
+ ),
+ activeNotificationModel(
+ key = thirdKey,
+ statusBarChipIcon = null,
+ promotedContent = null,
+ ),
+ )
+ )
+
+ assertThat(latest).hasSize(2)
+ assertIsNotifKey(latest!![0], firstKey)
+ assertIsNotifKey(latest!![1], secondKey)
+ }
+
+ @Test
fun chips_clickingChipNotifiesInteractor() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -151,7 +212,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "clickTest",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("clickTest").build(),
)
)
)
@@ -171,9 +233,17 @@ class NotifChipsViewModelTest : SysuiTestCase() {
companion object {
fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ assertThat(latest)
+ .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
}
+
+ fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
+ assertThat(latest)
+ .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
+ }
}
}
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 eb0978eff24b..b2e7febd1743 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
@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.commandline.commandRegistry
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.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -307,7 +308,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
@@ -328,12 +329,14 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
),
)
)
@@ -355,17 +358,20 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
),
)
)
@@ -386,12 +392,14 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = true,
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
),
)
)
@@ -412,7 +420,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- isPromoted = true,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 2a3878c17a1b..5247433e0019 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.connectivity
import android.content.Context
import android.os.UserHandle
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
@@ -251,9 +252,25 @@ class AccessPointControllerImplTest : SysuiTestCase() {
val primaryUserMockContext = mock<Context>()
mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
controller.onUserSwitched(PRIMARY_USER_ID)
+
// Create is expected to be called once when the test starts and a second time when the user
- // is switched.
+ // is switched. The first WifiPickerTracker should have its onStop() method called prior to
+ // the new WifiPickerTracker being created.
verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ verify(wifiPickerTracker).onStop()
+ }
+
+ @Test
+ @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun switchUsers_flagDisabled() {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
+ controller.onUserSwitched(PRIMARY_USER_ID)
+
+ // Create is expected to only be called once when the test starts, switching users should
+ // have no effects.
+ verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+ verify(wifiPickerTracker, never()).onStop()
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
index 1b3f29a6f0c5..dd1b36925363 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.core
+import android.internal.statusbar.FakeStatusBarService.Companion.SECONDARY_DISPLAY_ID
import android.internal.statusbar.fakeStatusBarService
import android.platform.test.annotations.EnableFlags
import android.view.WindowInsets
@@ -53,7 +54,7 @@ class CommandQueueInitializerTest : SysuiTestCase() {
}
@Test
- fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fun start_defaultDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
initializer.start()
@@ -62,7 +63,7 @@ class CommandQueueInitializerTest : SysuiTestCase() {
}
@Test
- fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fun start_defaultDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
initializer.start()
@@ -71,6 +72,32 @@ class CommandQueueInitializerTest : SysuiTestCase() {
}
@Test
+ fun start_secondaryDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.statusBars()
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+ .isTrue()
+ // Default display should be unaffected
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+ }
+
+ @Test
+ fun start_secondaryDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.navigationBars()
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+ .isFalse()
+ // Default display should be unaffected
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+ }
+
+ @Test
fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
initializer.start()
@@ -85,6 +112,17 @@ class CommandQueueInitializerTest : SysuiTestCase() {
fakeStatusBarService.packageName,
fakeStatusBarService.letterboxDetails,
)
+ verify(commandQueueCallbacks)
+ .onSystemBarAttributesChanged(
+ SECONDARY_DISPLAY_ID,
+ fakeStatusBarService.appearanceSecondaryDisplay,
+ fakeStatusBarService.appearanceRegionsSecondaryDisplay,
+ fakeStatusBarService.navbarColorManagedByImeSecondaryDisplay,
+ fakeStatusBarService.behaviorSecondaryDisplay,
+ fakeStatusBarService.requestedVisibleTypesSecondaryDisplay,
+ fakeStatusBarService.packageNameSecondaryDisplay,
+ fakeStatusBarService.letterboxDetailsSecondaryDisplay,
+ )
}
@Test
@@ -105,6 +143,14 @@ class CommandQueueInitializerTest : SysuiTestCase() {
fakeStatusBarService.imeBackDisposition,
fakeStatusBarService.showImeSwitcher,
)
+
+ verify(commandQueueCallbacks)
+ .setImeWindowStatus(
+ SECONDARY_DISPLAY_ID,
+ fakeStatusBarService.imeWindowVisSecondaryDisplay,
+ fakeStatusBarService.imeBackDispositionSecondaryDisplay,
+ fakeStatusBarService.showImeSwitcherSecondaryDisplay,
+ )
}
@Test
@@ -117,6 +163,11 @@ class CommandQueueInitializerTest : SysuiTestCase() {
.isEqualTo(fakeStatusBarService.disabledFlags1)
assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
.isEqualTo(fakeStatusBarService.disabledFlags2)
+
+ assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(fakeStatusBarService.disabledFlags1SecondaryDisplay)
+ assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(fakeStatusBarService.disabledFlags2SecondaryDisplay)
}
@Test
@@ -125,5 +176,7 @@ class CommandQueueInitializerTest : SysuiTestCase() {
assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+ assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
+ assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 689fc7cb647b..68798a88eecc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
@@ -22,83 +24,92 @@ import android.app.NotificationManager
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.notification.StatusBarNotification
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.plugins.statusbar.fakeStatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.dynamicPrivacyController
+import com.android.systemui.statusbar.notification.mockDynamicPrivacyController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController
+import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController
import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+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 SensitiveContentCoordinatorTest : SysuiTestCase() {
-
- val kosmos = testKosmos()
-
- val dynamicPrivacyController: DynamicPrivacyController = mock()
- val lockscreenUserManager: NotificationLockscreenUserManager = mock()
- val pipeline: NotifPipeline = mock()
- val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
- val statusBarStateController: StatusBarStateController = mock()
- val keyguardStateController: KeyguardStateController = mock()
- val mSelectedUserInteractor: SelectedUserInteractor = mock()
+@RunWith(ParameterizedAndroidJunit4::class)
+class SensitiveContentCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ val kosmos =
+ testKosmos().apply {
+ // Override some Kosmos objects with mocks or fakes for easier testability
+ dynamicPrivacyController = mockDynamicPrivacyController
+ sensitiveNotificationProtectionController =
+ mockSensitiveNotificationProtectionController
+ statusBarStateController = fakeStatusBarStateController
+ }
+
+ val dynamicPrivacyController: DynamicPrivacyController = kosmos.mockDynamicPrivacyController
+ val lockscreenUserManager: NotificationLockscreenUserManager =
+ kosmos.notificationLockscreenUserManager
+ val pipeline: NotifPipeline = kosmos.notifPipeline
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ val statusBarStateController: SysuiStatusBarStateController =
+ kosmos.fakeStatusBarStateController
val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
- mock()
- val deviceEntryInteractor: DeviceEntryInteractor = mock()
- val sceneInteractor: SceneInteractor = mock()
-
- val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent.factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController,
- mSelectedUserInteractor,
- sensitiveNotificationProtectionController,
- deviceEntryInteractor,
- sceneInteractor,
- kosmos.applicationCoroutineScope,
- )
- .coordinator
+ kosmos.mockSensitiveNotificationProtectionController
+ val sceneInteractor: SceneInteractor = kosmos.sceneInteractor
+
+ val coordinator: SensitiveContentCoordinator by lazy { kosmos.sensitiveContentCoordinator }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -143,7 +154,7 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
fun screenshareSecretFilter_flagDisabled_filterNoAdded() {
coordinator.attach(pipeline)
- verify(pipeline, never()).addFinalizeFilter(any(NotifFilter::class.java))
+ verify(pipeline, never()).addFinalizeFilter(any())
}
@Test
@@ -675,13 +686,13 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
- whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
.thenReturn(true)
val entry = fakeNotification(2, true)
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
.thenReturn(true)
+ statusBarStateController.state = StatusBarState.KEYGUARD
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -733,26 +744,3 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
return notificationEntry
}
}
-
-@CoordinatorScope
-@Component(modules = [SensitiveContentCoordinatorModule::class])
-interface TestSensitiveContentCoordinatorComponent {
- val coordinator: SensitiveContentCoordinator
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance dynamicPrivacyController: DynamicPrivacyController,
- @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
- @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
- @BindsInstance statusBarStateController: StatusBarStateController,
- @BindsInstance keyguardStateController: KeyguardStateController,
- @BindsInstance selectedUserInteractor: SelectedUserInteractor,
- @BindsInstance
- sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
- @BindsInstance deviceEntryInteractor: DeviceEntryInteractor,
- @BindsInstance sceneInteractor: SceneInteractor,
- @BindsInstance @Application scope: CoroutineScope,
- ): TestSensitiveContentCoordinatorComponent
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index ba85e32484df..657fffb1875d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -45,6 +46,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.BrokenWithSceneContainer;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -68,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
@@ -110,6 +114,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Mock private VisualStabilityCoordinatorLogger mLogger;
+ @Mock private KeyguardStateController mKeyguardStateController;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -155,6 +160,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mKosmos.getCommunalSceneInteractor(),
mKosmos.getShadeInteractor(),
mKosmos.getKeyguardTransitionInteractor(),
+ mKeyguardStateController,
mLogger);
mCoordinator.attach(mNotifPipeline);
mTestScope.getTestScheduler().runCurrent();
@@ -539,6 +545,25 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
@EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ @DisableSceneContainer
+ public void testNotLockscreenInGoneTransitionLegacy_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ doReturn(true).when(mKeyguardStateController).isKeyguardFadingAway();
+ mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ doReturn(false).when(mKeyguardStateController).isKeyguardFadingAway();
+ mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ @EnableSceneContainer
@BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
public void testNotLockscreenInGoneTransition_invalidationCalled() {
// GIVEN visual stability is being maintained b/c animation is playing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 99bda856818e..54ce88b40c11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification
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.setActiveNotifs
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -169,8 +170,12 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.promotedOngoingNotifications)
- val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
- val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+ val promoted1 =
+ activeNotificationModel(
+ key = "notif1",
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
@@ -189,9 +194,9 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
- .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
- .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
- .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+ .apply { activeNotificationModel(key = "notif1", promotedContent = null) }
+ .apply { activeNotificationModel(key = "notif2", promotedContent = null) }
+ .apply { activeNotificationModel(key = "notif3", promotedContent = null) }
.build()
assertThat(latest!!).isEmpty()
@@ -203,10 +208,18 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.promotedOngoingNotifications)
- val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
- val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
- val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
- val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+ val promoted1 =
+ activeNotificationModel(
+ key = "notif1",
+ promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ )
+ val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
+ val notPromoted3 = activeNotificationModel(key = "notif3", promotedContent = null)
+ val promoted4 =
+ activeNotificationModel(
+ key = "notif4",
+ promotedContent = PromotedNotificationContentModel.Builder("notif4").build(),
+ )
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 183f9016a23b..5d9aa71c5d89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.app.Notification
-import android.app.Notification.FLAG_PROMOTED_ONGOING
import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -29,7 +28,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.byKey
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
@@ -48,11 +47,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
private val notifsRepository = kosmos.activeNotificationListRepository
private val notifsInteractor = kosmos.activeNotificationsInteractor
private val underTest =
- RenderNotificationListInteractor(
- notifsRepository,
- sectionStyleProvider = mock(),
- promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
- )
+ RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock())
@Test
fun setRenderedList_preservesOrdering() =
@@ -127,12 +122,16 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
- fun setRenderList_setsPromotionStatus() =
+ fun setRenderList_setsPromotionContent() =
testScope.runTest {
val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
- val notPromoted1 = mockNotificationEntry("key1", flag = null)
- val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+ val notPromoted1 = mockNotificationEntry("key1", promotedContent = null)
+ val promoted2 =
+ mockNotificationEntry(
+ "key2",
+ promotedContent = PromotedNotificationContentModel.Builder("key2").build(),
+ )
underTest.setRenderedList(listOf(notPromoted1, promoted2))
@@ -140,22 +139,19 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
val first = actual!![0]
assertThat(first.key).isEqualTo("key1")
- assertThat(first.isPromoted).isFalse()
+ assertThat(first.promotedContent).isNull()
val second = actual!![1]
assertThat(second.key).isEqualTo("key2")
- assertThat(second.isPromoted).isTrue()
+ assertThat(second.promotedContent).isNotNull()
}
private fun mockNotificationEntry(
key: String,
rank: Int = 0,
- flag: Int? = null,
+ promotedContent: PromotedNotificationContentModel? = null,
): NotificationEntry {
val nBuilder = Notification.Builder(context, "a")
- if (flag != null) {
- nBuilder.setFlag(flag, true)
- }
val notification = nBuilder.build()
val mockSbn =
@@ -169,6 +165,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
whenever(this.representativeEntry).thenReturn(this)
whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
whenever(this.sbn).thenReturn(mockSbn)
+ whenever(this.promotedNotificationContentModel).thenReturn(promotedContent)
}
}
}
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 22a9c64d2cc9..31a2bd0371aa 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
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.time.FakeSystemClock
@@ -97,7 +98,7 @@ class AvalancheControllerTest : SysuiTestCase() {
AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
- TestableHeadsUpManager(
+ HeadsUpManagerImpl(
mContext,
mLogger,
kosmos.statusBarStateController,
@@ -105,9 +106,10 @@ class AvalancheControllerTest : SysuiTestCase() {
GroupMembershipManagerImpl(),
kosmos.visualStabilityProvider,
kosmos.configurationController,
- mExecutor,
+ mockExecutorHandler(mExecutor),
mGlobalSettings,
mSystemClock,
+ mExecutor,
mAccessibilityMgr,
mUiEventLoggerFake,
JavaAdapter(kosmos.testScope),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java
deleted file mode 100644
index 01f78cb289fd..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.headsup;
-
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-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;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-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;
-
-import kotlinx.coroutines.flow.StateFlowKt;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(ParameterizedAndroidJunit4.class)
-// TODO(b/378142453): Merge this with HeadsUpManagerPhoneTest.
-public class HeadsUpManagerImplTest extends SysuiTestCase {
- protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
- static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
-
- private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
-
- private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
- @Mock private Handler mBgHandler;
- @Mock private DumpManager dumpManager;
- @Mock private ShadeInteractor mShadeInteractor;
- private AvalancheController mAvalancheController;
-
- @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
-
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
- protected static final int TEST_AUTO_DISMISS_TIME = 600;
- protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
- // Number of notifications to use in tests requiring multiple notifications
- private static final int TEST_NUM_NOTIFICATIONS = 4;
-
- protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
- protected final FakeSystemClock mSystemClock = new FakeSystemClock();
- protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
- @Mock protected ExpandableNotificationRow mRow;
-
- static {
- assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
- assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
- assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
- }
-
- private HeadsUpManagerImpl createHeadsUpManager() {
- return new TestableHeadsUpManager(
- mContext,
- mLogger,
- mKosmos.getStatusBarStateController(),
- mKosmos.getKeyguardBypassController(),
- new GroupMembershipManagerImpl(),
- mKosmos.getVisualStabilityProvider(),
- mKosmos.getConfigurationController(),
- mExecutor,
- mGlobalSettings,
- mSystemClock,
- mAccessibilityMgr,
- mUiEventLoggerFake,
- new JavaAdapter(mKosmos.getTestScope()),
- mShadeInteractor,
- mAvalancheController);
- }
-
- private NotificationEntry createStickyEntry(int id) {
- final Notification notif = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
- .build();
- return HeadsUpManagerTestUtil.createEntry(id, notif);
- }
-
- private NotificationEntry createStickyForSomeTimeEntry(int id) {
- final Notification notif = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
- .build();
- return HeadsUpManagerTestUtil.createEntry(id, notif);
- }
-
- private void useAccessibilityTimeout(boolean use) {
- if (use) {
- doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
- .getRecommendedTimeoutMillis(anyInt(), anyInt());
- } else {
- when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
- i -> i.getArgument(0));
- }
- }
-
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
- }
-
- public HeadsUpManagerImplTest(FlagsParameterization flags) {
- mSetFlagsRule.setFlagsParameterization(flags);
- }
-
- @Override
- public void SysuiSetup() throws Exception {
- super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
- mBgHandler);
- when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(true));
- when(mKosmos.getKeyguardBypassController().getBypassEnabled()).thenReturn(false);
- }
-
- @Test
- public void testHasNotifications_headsUpManagerMapNotEmpty_true() {
- final HeadsUpManagerImpl bhum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- bhum.showNotification(entry);
-
- assertThat(bhum.mHeadsUpEntryMap).isNotEmpty();
- assertThat(bhum.hasNotifications()).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testHasNotifications_avalancheMapNotEmpty_true() {
- final HeadsUpManagerImpl bhum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
- mAvalancheController.addToNext(headsUpEntry, () -> {});
-
- assertThat(mAvalancheController.getWaitingEntryList()).isNotEmpty();
- assertThat(bhum.hasNotifications()).isTrue();
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testHasNotifications_false() {
- final HeadsUpManagerImpl bhum = createHeadsUpManager();
- assertThat(bhum.mHeadsUpEntryMap).isEmpty();
- assertThat(mAvalancheController.getWaitingEntryList()).isEmpty();
- assertThat(bhum.hasNotifications()).isFalse();
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testGetHeadsUpEntryList_includesAvalancheEntryList() {
- final HeadsUpManagerImpl bhum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
- mAvalancheController.addToNext(headsUpEntry, () -> {});
-
- assertThat(bhum.getHeadsUpEntryList()).contains(headsUpEntry);
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testGetHeadsUpEntry_returnsAvalancheEntry() {
- final HeadsUpManagerImpl bhum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
- mAvalancheController.addToNext(headsUpEntry, () -> {});
-
- assertThat(bhum.getHeadsUpEntry(notifEntry.getKey())).isEqualTo(headsUpEntry);
- }
-
- @Test
- public void testShowNotification_addsEntry() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- alm.showNotification(entry);
-
- assertTrue(alm.isHeadsUpEntry(entry.getKey()));
- assertTrue(alm.hasNotifications());
- assertEquals(entry, alm.getEntry(entry.getKey()));
- }
-
- @Test
- public void testShowNotification_autoDismisses() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- alm.showNotification(entry);
- mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
- assertFalse(alm.isHeadsUpEntry(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_removeDeferred() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ false, "removeDeferred");
- assertFalse(removedImmediately);
- assertTrue(alm.isHeadsUpEntry(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_forceRemove() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ true, "forceRemove");
- assertTrue(removedImmediately);
- assertFalse(alm.isHeadsUpEntry(entry.getKey()));
- }
-
- @Test
- public void testReleaseAllImmediately() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(i, mContext);
- entry.setRow(mRow);
- alm.showNotification(entry);
- }
-
- alm.releaseAllImmediately();
-
- assertEquals(0, alm.getAllEntries().count());
- }
-
- @Test
- public void testCanRemoveImmediately_notShownLongEnough() {
- final HeadsUpManagerImpl alm = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- alm.showNotification(entry);
-
- // The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(entry.getKey()));
- }
-
- @Test
- public void testHunRemovedLogging() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = mock(
- HeadsUpManagerImpl.HeadsUpEntry.class);
- when(headsUpEntry.getPinnedStatus())
- .thenReturn(StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned));
- headsUpEntry.mEntry = notifEntry;
-
- hum.onEntryRemoved(headsUpEntry, "test");
-
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
- }
-
-
- @Test
- public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testShowNotification_autoDismissesWithDefaultTimeout() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
- + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
- assertFalse(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
- + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testShowNotification_sticky_neverAutoDismisses() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = createStickyEntry(/* id = */ 0);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- useAccessibilityTimeout(true);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
- + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
- useAccessibilityTimeout(true);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
- + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testRemoveNotification_beforeMinimumDisplayTime() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
-
- final boolean removedImmediately = hum.removeNotification(
- entry.getKey(), /* releaseImmediately = */ false, "beforeMinimumDisplayTime");
- assertFalse(removedImmediately);
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-
- mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-
- assertFalse(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testRemoveNotification_afterMinimumDisplayTime() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- useAccessibilityTimeout(false);
-
- hum.showNotification(entry);
- mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-
- assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-
- final boolean removedImmediately = hum.removeNotification(
- entry.getKey(), /* releaseImmediately = */ false, "afterMinimumDisplayTime");
- assertTrue(removedImmediately);
- assertFalse(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testRemoveNotification_releaseImmediately() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hum.showNotification(entry);
-
- final boolean removedImmediately = hum.removeNotification(
- entry.getKey(), /* releaseImmediately = */ true, "afterMinimumDisplayTime");
- assertTrue(removedImmediately);
- assertFalse(hum.isHeadsUpEntry(entry.getKey()));
- }
-
-
- @Test
- public void testIsSticky_rowPinnedAndExpanded_true() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
- when(mRow.isPinned()).thenReturn(true);
- notifEntry.setRow(mRow);
-
- hum.showNotification(notifEntry);
-
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
- notifEntry.getKey());
- headsUpEntry.setExpanded(true);
-
- assertTrue(hum.isSticky(notifEntry.getKey()));
- }
-
- @Test
- public void testIsSticky_remoteInputActive_true() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
-
- hum.showNotification(notifEntry);
-
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
- notifEntry.getKey());
- headsUpEntry.mRemoteInputActive = true;
-
- assertTrue(hum.isSticky(notifEntry.getKey()));
- }
-
- @Test
- public void testIsSticky_hasFullScreenIntent_true() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry =
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
- hum.showNotification(notifEntry);
-
- assertTrue(hum.isSticky(notifEntry.getKey()));
- }
-
-
- @Test
- public void testIsSticky_stickyForSomeTime_false() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
-
- hum.showNotification(entry);
-
- assertFalse(hum.isSticky(entry.getKey()));
- }
-
-
- @Test
- public void testIsSticky_false() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
-
- hum.showNotification(notifEntry);
-
- final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
- notifEntry.getKey());
- headsUpEntry.setExpanded(false);
- headsUpEntry.mRemoteInputActive = false;
-
- assertFalse(hum.isSticky(notifEntry.getKey()));
- }
-
- @Test
- public void testCompareTo_withNullEntries() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-
- hum.showNotification(alertEntry);
-
- assertThat(hum.compare(alertEntry, null)).isLessThan(0);
- assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
- assertThat(hum.compare(null, null)).isEqualTo(0);
- }
-
- @Test
- public void testCompareTo_withNonAlertEntries() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
-
- final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
- "nae1").build();
- final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
- "nae2").build();
- final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- hum.showNotification(alertEntry);
-
- assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
- assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
- assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
- }
-
- @Test
- public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
-
- final HeadsUpManagerImpl.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(
- new NotificationEntryBuilder()
- .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
- new Notification.Builder(mContext, "")
- .setCategory(Notification.CATEGORY_CALL)
- .setOngoing(true)))
- .build());
-
- final HeadsUpManagerImpl.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
- HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
- activeRemoteInput.mRemoteInputActive = true;
-
- assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
- assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0);
- }
-
- @Test
- public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
-
- final Person person = new Person.Builder().setName("person").build();
- final PendingIntent intent = mock(PendingIntent.class);
- final HeadsUpManagerImpl.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(
- new NotificationEntryBuilder()
- .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
- new Notification.Builder(mContext, "")
- .setStyle(Notification.CallStyle
- .forIncomingCall(person, intent, intent))))
- .build());
-
- final HeadsUpManagerImpl.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
- HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
- activeRemoteInput.mRemoteInputActive = true;
-
- assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
- assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0);
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testPinEntry_logsPeek_throttleEnabled() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
-
- // Needs full screen intent in order to be pinned
- final HeadsUpManagerImpl.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
-
- // Note: the standard way to show a notification would be calling showNotification rather
- // than onAlertEntryAdded. However, in practice showNotification in effect adds
- // the notification and then updates it; in order to not log twice, the entry needs
- // to have a functional ExpandableNotificationRow that can keep track of whether it's
- // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- hum.onEntryAdded(entryToPin);
-
- assertEquals(2, mUiEventLoggerFake.numLogs());
- assertEquals(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId(),
- mUiEventLoggerFake.eventId(0));
- assertEquals(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
- mUiEventLoggerFake.eventId(1));
- }
-
- @Test
- @DisableFlags(NotificationThrottleHun.FLAG_NAME)
- public void testPinEntry_logsPeek_throttleDisabled() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
-
- // Needs full screen intent in order to be pinned
- final HeadsUpManagerImpl.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
-
- // Note: the standard way to show a notification would be calling showNotification rather
- // than onAlertEntryAdded. However, in practice showNotification in effect adds
- // the notification and then updates it; in order to not log twice, the entry needs
- // to have a functional ExpandableNotificationRow that can keep track of whether it's
- // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- hum.onEntryAdded(entryToPin);
-
- assertEquals(1, mUiEventLoggerFake.numLogs());
- assertEquals(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
- mUiEventLoggerFake.eventId(0));
- }
-
- @Test
- public void testSetUserActionMayIndirectlyRemove() {
- final HeadsUpManagerImpl hum = createHeadsUpManager();
- final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
- mContext);
-
- hum.showNotification(notifEntry);
-
- assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
-
- hum.setUserActionMayIndirectlyRemove(notifEntry);
-
- assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
- }
-}
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
new file mode 100644
index 000000000000..8420c49755b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.headsup
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+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
+import android.testing.TestableLooper.RunWithLooper
+import android.view.accessibility.accessibilityManager
+import android.view.accessibility.accessibilityManagerWrapper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.flags.BrokenWithSceneContainer
+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.res.R
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+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.GroupMembershipManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+
+ private val groupManager = mock<GroupMembershipManager>()
+ private val bgHandler = mock<Handler>()
+ private val headsUpManagerLogger = mock<HeadsUpManagerLogger>()
+
+ val statusBarStateController = kosmos.sysuiStatusBarStateController
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val systemClock = kosmos.fakeSystemClock
+ private val executor = kosmos.fakeExecutor
+ private val uiEventLoggerFake = kosmos.uiEventLoggerFake
+ private val javaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
+
+ private lateinit var testHelper: NotificationTestHelper
+ private lateinit var avalancheController: AvalancheController
+ private lateinit var underTest: HeadsUpManagerImpl
+
+ @Before
+ fun setUp() {
+ mContext.getOrCreateTestableResources().apply {
+ this.addOverride(R.integer.ambient_notification_extension_time, TEST_EXTENSION_TIME)
+ this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME)
+ this.addOverride(
+ R.integer.heads_up_notification_minimum_time,
+ TEST_MINIMUM_DISPLAY_TIME,
+ )
+ this.addOverride(
+ R.integer.heads_up_notification_minimum_time_with_throttling,
+ TEST_MINIMUM_DISPLAY_TIME,
+ )
+ this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME)
+ this.addOverride(
+ R.integer.sticky_heads_up_notification_time,
+ TEST_STICKY_AUTO_DISMISS_TIME,
+ )
+ }
+
+ allowTestableLooperAsMainThread()
+ testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+
+ whenever(kosmos.keyguardBypassController.bypassEnabled).thenReturn(false)
+ kosmos.visualStabilityProvider.isReorderingAllowed = true
+ avalancheController =
+ AvalancheController(
+ kosmos.dumpManager,
+ uiEventLoggerFake,
+ headsUpManagerLogger,
+ bgHandler,
+ )
+ underTest =
+ HeadsUpManagerImpl(
+ mContext,
+ headsUpManagerLogger,
+ statusBarStateController,
+ kosmos.keyguardBypassController,
+ groupManager,
+ kosmos.visualStabilityProvider,
+ kosmos.configurationController,
+ mockExecutorHandler(executor),
+ globalSettings,
+ systemClock,
+ executor,
+ kosmos.accessibilityManagerWrapper,
+ uiEventLoggerFake,
+ javaAdapter,
+ kosmos.shadeInteractor,
+ avalancheController,
+ )
+ }
+
+ @Test
+ fun testHasNotifications_headsUpManagerMapNotEmpty_true() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(entry)
+
+ assertThat(underTest.mHeadsUpEntryMap).isNotEmpty()
+ assertThat(underTest.hasNotifications()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testHasNotifications_avalancheMapNotEmpty_true() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+ avalancheController.addToNext(headsUpEntry) {}
+
+ assertThat(avalancheController.getWaitingEntryList()).isNotEmpty()
+ assertThat(underTest.hasNotifications()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testHasNotifications_false() {
+ assertThat(underTest.mHeadsUpEntryMap).isEmpty()
+ assertThat(avalancheController.getWaitingEntryList()).isEmpty()
+ assertThat(underTest.hasNotifications()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testGetHeadsUpEntryList_includesAvalancheEntryList() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+ avalancheController.addToNext(headsUpEntry) {}
+
+ assertThat(underTest.headsUpEntryList).contains(headsUpEntry)
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testGetHeadsUpEntry_returnsAvalancheEntry() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+ avalancheController.addToNext(headsUpEntry) {}
+
+ assertThat(underTest.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testShowNotification_addsEntry() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ assertThat(underTest.hasNotifications()).isTrue()
+ assertThat(underTest.getEntry(entry.key)).isEqualTo(entry)
+ }
+
+ @Test
+ fun testShowNotification_autoDismisses() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong())
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testRemoveNotification_removeDeferred() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately= */ false,
+ "removeDeferred",
+ )
+ assertThat(removedImmediately).isFalse()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testRemoveNotification_forceRemove() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+
+ val removedImmediately =
+ underTest.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove")
+ assertThat(removedImmediately).isTrue()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testReleaseAllImmediately() {
+ for (i in 0 until 4) {
+ val entry = HeadsUpManagerTestUtil.createEntry(i, mContext)
+ entry.row = mock<ExpandableNotificationRow>()
+ underTest.showNotification(entry)
+ }
+
+ underTest.releaseAllImmediately()
+
+ assertThat(underTest.allEntries.count()).isEqualTo(0)
+ }
+
+ @Test
+ fun testCanRemoveImmediately_notShownLongEnough() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+
+ // The entry has just been added so we should not remove immediately.
+ assertThat(underTest.canRemoveImmediately(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testHunRemovedLogging() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val headsUpEntry = underTest.HeadsUpEntry(notifEntry)
+ headsUpEntry.setRowPinnedStatus(PinnedStatus.NotPinned)
+
+ underTest.onEntryRemoved(headsUpEntry, "test")
+
+ verify(headsUpManagerLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry))
+ }
+
+ @Test
+ fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong())
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testShowNotification_autoDismissesWithDefaultTimeout() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(
+ (TEST_TOUCH_ACCEPTANCE_TIME +
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+ .toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testRemoveNotification_beforeMinimumDisplayTime() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately = */ false,
+ "beforeMinimumDisplayTime",
+ )
+ assertThat(removedImmediately).isFalse()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+ systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testRemoveNotification_afterMinimumDisplayTime() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately = */ false,
+ "afterMinimumDisplayTime",
+ )
+ assertThat(removedImmediately).isTrue()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testRemoveNotification_releaseImmediately() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(entry)
+
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately = */ true,
+ "afterMinimumDisplayTime",
+ )
+ assertThat(removedImmediately).isTrue()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testSnooze() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(entry)
+ underTest.snooze()
+ assertThat(underTest.isSnoozed(entry.sbn.packageName)).isTrue()
+ }
+
+ @Test
+ fun testSwipedOutNotification() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(entry)
+ underTest.addSwipedOutNotification(entry.key)
+
+ // Remove should succeed because the notification is swiped out
+ val removedImmediately =
+ underTest.removeNotification(
+ entry.key,
+ /* releaseImmediately= */ false,
+ /* reason= */ "swipe out",
+ )
+ assertThat(removedImmediately).isTrue()
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testCanRemoveImmediately_swipedOut() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(entry)
+ underTest.addSwipedOutNotification(entry.key)
+
+ // Notification is swiped so it can be immediately removed.
+ assertThat(underTest.canRemoveImmediately(entry.key)).isTrue()
+ }
+
+ @Ignore("b/141538055")
+ @Test
+ fun testCanRemoveImmediately_notTopEntry() {
+ val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
+ laterEntry.row = mock<ExpandableNotificationRow>()
+ underTest.showNotification(earlierEntry)
+ underTest.showNotification(laterEntry)
+
+ // Notification is "behind" a higher priority notification so we can remove it immediately.
+ assertThat(underTest.canRemoveImmediately(earlierEntry.key)).isTrue()
+ }
+
+ @Test
+ fun testExtendHeadsUp() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(entry)
+ underTest.extendHeadsUp()
+ systemClock.advanceTime(((TEST_AUTO_DISMISS_TIME + TEST_EXTENSION_TIME) / 2).toLong())
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testShowNotification_removeWhenReorderingAllowedTrue() {
+ kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(notifEntry)
+ assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
+ }
+
+ class TestAnimationStateHandler : AnimationStateHandler {
+ override fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean) {}
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testReorderingAllowed_clearsListOfEntriesToRemove() {
+ kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(notifEntry)
+ assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
+
+ underTest.setAnimationStateHandler(TestAnimationStateHandler())
+ underTest.mOnReorderingAllowedListener.onReorderingAllowed()
+ assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.isEmpty()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testShowNotification_reorderNotAllowed_seenInShadeTrue() {
+ kosmos.visualStabilityProvider.isReorderingAllowed = false
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(notifEntry)
+ assertThat(notifEntry.isSeenInShade).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testShowNotification_reorderAllowed_seenInShadeFalse() {
+ kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ underTest.showNotification(notifEntry)
+ assertThat(notifEntry.isSeenInShade).isFalse()
+ }
+
+ @Test
+ fun testShowNotification_sticky_neverAutoDismisses() {
+ val entry = createStickyEntry(id = 0)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(
+ (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testShowNotification_autoDismissesWithAccessibilityTimeout() {
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ useAccessibilityTimeout(true)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(
+ (TEST_TOUCH_ACCEPTANCE_TIME +
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+ .toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+ val entry = createStickyForSomeTimeEntry(id = 0)
+ useAccessibilityTimeout(false)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(
+ (TEST_TOUCH_ACCEPTANCE_TIME +
+ (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2)
+ .toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+ val entry = createStickyForSomeTimeEntry(id = 0)
+ useAccessibilityTimeout(true)
+
+ underTest.showNotification(entry)
+ systemClock.advanceTime(
+ (TEST_TOUCH_ACCEPTANCE_TIME +
+ (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+ .toLong()
+ )
+
+ assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+ }
+
+ @Test
+ fun testIsSticky_rowPinnedAndExpanded_true() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val row = testHelper.createRow()
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ notifEntry.row = row
+
+ underTest.showNotification(notifEntry)
+
+ val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.setExpanded(true)
+
+ assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+ }
+
+ @Test
+ fun testIsSticky_remoteInputActive_true() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(notifEntry)
+
+ val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.mRemoteInputActive = true
+
+ assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+ }
+
+ @Test
+ fun testIsSticky_hasFullScreenIntent_true() {
+ val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(notifEntry)
+
+ assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+ }
+
+ @Test
+ fun testIsSticky_stickyForSomeTime_false() {
+ val entry = createStickyForSomeTimeEntry(id = 0)
+
+ underTest.showNotification(entry)
+
+ assertThat(underTest.isSticky(entry.key)).isFalse()
+ }
+
+ @Test
+ fun testIsSticky_false() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(notifEntry)
+
+ val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.setExpanded(false)
+ headsUpEntry.mRemoteInputActive = false
+
+ assertThat(underTest.isSticky(notifEntry.key)).isFalse()
+ }
+
+ @Test
+ fun testShouldHeadsUpBecomePinned_noFSI_false() =
+ kosmos.runTest {
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
+ }
+
+ @Test
+ fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
+ kosmos.runTest {
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+ // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
+ underTest.showNotification(notifEntry)
+
+ val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.mWasUnpinned = false
+
+ assertThat(underTest.shouldHeadsUpBecomePinned(notifEntry)).isTrue()
+ }
+
+ @Test
+ fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
+ kosmos.runTest {
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ val notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+ // Add notifEntry to ANM mAlertEntries map and make it unpinned
+ underTest.showNotification(notifEntry)
+
+ val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+ headsUpEntry!!.mWasUnpinned = true
+
+ assertThat(underTest.shouldHeadsUpBecomePinned(notifEntry)).isFalse()
+ }
+
+ @Test
+ @BrokenWithSceneContainer(381869885) // because `ShadeTestUtil.setShadeExpansion(0f)`
+ // still causes `ShadeInteractor.isAnyExpanded` to emit `true`, when it should emit `false`.
+ fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+ kosmos.runTest {
+ // GIVEN
+ // TODO(b/381869885): We should be able to use `ShadeTestUtil.setShadeExpansion(0f)`
+ // instead.
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isTrue()
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+ kosmos.runTest {
+ // GIVEN
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+ kosmos.runTest {
+ // GIVEN
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(1207)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+ kosmos.runTest {
+ // GIVEN
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isTrue()
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+ kosmos.runTest {
+ // GIVEN
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+ kosmos.runTest {
+ // GIVEN
+ shadeTestUtil.setShadeExpansion(1f)
+ // TODO(b/381869885): Determine why we need both of these ShadeTestUtil calls.
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
+
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+
+ // THEN
+ assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
+ }
+
+ @Test
+ fun testCompareTo_withNullEntries() {
+ val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+
+ underTest.showNotification(alertEntry)
+
+ assertThat(underTest.compare(alertEntry, null)).isLessThan(0)
+ assertThat(underTest.compare(null, alertEntry)).isGreaterThan(0)
+ assertThat(underTest.compare(null, null)).isEqualTo(0)
+ }
+
+ @Test
+ fun testCompareTo_withNonAlertEntries() {
+ val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build()
+ val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build()
+ val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+ underTest.showNotification(alertEntry)
+
+ assertThat(underTest.compare(alertEntry, nonAlertEntry1)).isLessThan(0)
+ assertThat(underTest.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0)
+ assertThat(underTest.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0)
+ }
+
+ @Test
+ fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
+ val ongoingCall =
+ underTest.HeadsUpEntry(
+ NotificationEntryBuilder()
+ .setSbn(
+ HeadsUpManagerTestUtil.createSbn(
+ /* id = */ 0,
+ Notification.Builder(mContext, "")
+ .setCategory(Notification.CATEGORY_CALL)
+ .setOngoing(true),
+ )
+ )
+ .build()
+ )
+
+ val activeRemoteInput =
+ underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+ activeRemoteInput.mRemoteInputActive = true
+
+ assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0)
+ assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0)
+ }
+
+ @Test
+ fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
+ val person = Person.Builder().setName("person").build()
+ val intent = mock<PendingIntent>()
+ val incomingCall =
+ underTest.HeadsUpEntry(
+ NotificationEntryBuilder()
+ .setSbn(
+ HeadsUpManagerTestUtil.createSbn(
+ /* id = */ 0,
+ Notification.Builder(mContext, "")
+ .setStyle(
+ Notification.CallStyle.forIncomingCall(person, intent, intent)
+ ),
+ )
+ )
+ .build()
+ )
+
+ val activeRemoteInput =
+ underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+ activeRemoteInput.mRemoteInputActive = true
+
+ assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0)
+ assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0)
+ }
+
+ @Test
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testPinEntry_logsPeek_throttleEnabled() {
+ // Needs full screen intent in order to be pinned
+ val entryToPin =
+ underTest.HeadsUpEntry(
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+ )
+
+ // Note: the standard way to show a notification would be calling showNotification rather
+ // than onAlertEntryAdded. However, in practice showNotification in effect adds
+ // the notification and then updates it; in order to not log twice, the entry needs
+ // to have a functional ExpandableNotificationRow that can keep track of whether it's
+ // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+ underTest.onEntryAdded(entryToPin)
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+ assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId())
+ .isEqualTo(uiEventLoggerFake.eventId(0))
+ assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
+ .isEqualTo(uiEventLoggerFake.eventId(1))
+ }
+
+ @Test
+ @DisableFlags(NotificationThrottleHun.FLAG_NAME)
+ fun testPinEntry_logsPeek_throttleDisabled() {
+ // Needs full screen intent in order to be pinned
+ val entryToPin =
+ underTest.HeadsUpEntry(
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+ )
+
+ // Note: the standard way to show a notification would be calling showNotification rather
+ // than onAlertEntryAdded. However, in practice showNotification in effect adds
+ // the notification and then updates it; in order to not log twice, the entry needs
+ // to have a functional ExpandableNotificationRow that can keep track of whether it's
+ // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+ underTest.onEntryAdded(entryToPin)
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
+ .isEqualTo(uiEventLoggerFake.eventId(0))
+ }
+
+ @Test
+ fun testSetUserActionMayIndirectlyRemove() {
+ val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+ underTest.showNotification(notifEntry)
+
+ assertThat(underTest.canRemoveImmediately(notifEntry.key)).isFalse()
+
+ underTest.setUserActionMayIndirectlyRemove(notifEntry)
+
+ assertThat(underTest.canRemoveImmediately(notifEntry.key)).isTrue()
+ }
+
+ private fun createStickyEntry(id: Int): NotificationEntry {
+ val notif =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(mock<PendingIntent>(), /* highPriority= */ true)
+ .build()
+ return HeadsUpManagerTestUtil.createEntry(id, notif)
+ }
+
+ private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry {
+ val notif =
+ Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
+ .build()
+ return HeadsUpManagerTestUtil.createEntry(id, notif)
+ }
+
+ private fun useAccessibilityTimeout(use: Boolean) {
+ if (use) {
+ whenever(kosmos.accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(TEST_A11Y_AUTO_DISMISS_TIME)
+ } else {
+ doAnswer { it.getArgument(0) as Int }
+ .whenever(kosmos.accessibilityManager)
+ .getRecommendedTimeoutMillis(any(), any())
+ }
+ }
+
+ companion object {
+ const val TEST_TOUCH_ACCEPTANCE_TIME = 200
+ const val TEST_A11Y_AUTO_DISMISS_TIME = 1000
+ const val TEST_EXTENSION_TIME = 500
+
+ const val TEST_MINIMUM_DISPLAY_TIME = 400
+ const val TEST_AUTO_DISMISS_TIME = 600
+ const val TEST_STICKY_AUTO_DISMISS_TIME = 800
+
+ init {
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME)
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME)
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME)
+ }
+
+ @get:Parameters(name = "{0}")
+ @JvmStatic
+ val flags: List<FlagsParameterization>
+ get() = buildList {
+ addAll(
+ FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
+ .andSceneContainer()
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt
deleted file mode 100644
index 35d825310fdc..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.headsup
-
-import android.os.Handler
-import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.FlagsParameterization
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.FakeStatusBarStateController
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
-import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.mockExecutorHandler
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.kotlin.whenever
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-@RunWithLooper
-class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : HeadsUpManagerImplTest(flags) {
-
- private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- @Mock private lateinit var mGroupManager: GroupMembershipManager
-
- @Mock private lateinit var mVSProvider: VisualStabilityProvider
-
- val statusBarStateController = FakeStatusBarStateController()
-
- @Mock private lateinit var mBypassController: KeyguardBypassController
-
- @Mock private lateinit var mConfigurationController: ConfigurationControllerImpl
-
- @Mock private lateinit var mAccessibilityManagerWrapper: AccessibilityManagerWrapper
-
- @Mock private lateinit var mUiEventLogger: UiEventLogger
-
- private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
-
- @Mock private lateinit var mShadeInteractor: ShadeInteractor
- @Mock private lateinit var dumpManager: DumpManager
- private lateinit var mAvalancheController: AvalancheController
-
- @Mock private lateinit var mBgHandler: Handler
-
- private fun createHeadsUpManagerPhone(): HeadsUpManagerImpl {
- return HeadsUpManagerImpl(
- mContext,
- mHeadsUpManagerLogger,
- statusBarStateController,
- mBypassController,
- mGroupManager,
- mVSProvider,
- mConfigurationController,
- mockExecutorHandler(mExecutor),
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController,
- )
- }
-
- @Before
- fun setUp() {
- whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
- whenever(mShadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
- whenever(mBypassController.bypassEnabled).thenReturn(false)
- whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
- val accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
- whenever(
- accessibilityMgr.getRecommendedTimeoutMillis(
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- )
- )
- .thenReturn(TEST_AUTO_DISMISS_TIME)
- mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
- mContext
- .getOrCreateTestableResources()
- .addOverride(R.integer.ambient_notification_extension_time, 500)
- mAvalancheController =
- AvalancheController(dumpManager, mUiEventLogger, mHeadsUpManagerLogger, mBgHandler)
- }
-
- @Test
- fun testSnooze() {
- val hmp: HeadsUpManager = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(entry)
- hmp.snooze()
- Assert.assertTrue(hmp.isSnoozed(entry.sbn.packageName))
- }
-
- @Test
- fun testSwipedOutNotification() {
- val hmp: HeadsUpManager = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(entry)
- hmp.addSwipedOutNotification(entry.key)
-
- // Remove should succeed because the notification is swiped out
- val removedImmediately =
- hmp.removeNotification(
- entry.key,
- /* releaseImmediately= */ false,
- /* reason= */ "swipe out",
- )
- Assert.assertTrue(removedImmediately)
- Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
- }
-
- @Test
- fun testCanRemoveImmediately_swipedOut() {
- val hmp: HeadsUpManager = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(entry)
- hmp.addSwipedOutNotification(entry.key)
-
- // Notification is swiped so it can be immediately removed.
- Assert.assertTrue(hmp.canRemoveImmediately(entry.key))
- }
-
- @Ignore("b/141538055")
- @Test
- fun testCanRemoveImmediately_notTopEntry() {
- val hmp: HeadsUpManager = createHeadsUpManagerPhone()
- val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
- laterEntry.row = mRow
- hmp.showNotification(earlierEntry)
- hmp.showNotification(laterEntry)
-
- // Notification is "behind" a higher priority notification so we can remove it immediately.
- Assert.assertTrue(hmp.canRemoveImmediately(earlierEntry.key))
- }
-
- @Test
- fun testExtendHeadsUp() {
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(entry)
- hmp.extendHeadsUp()
- mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
- Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_removeWhenReorderingAllowedTrue() {
- whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
- val hmp = createHeadsUpManagerPhone()
-
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(notifEntry)
- assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
- }
-
- class TestAnimationStateHandler : AnimationStateHandler {
- override fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean) {}
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testReorderingAllowed_clearsListOfEntriesToRemove() {
- whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
- val hmp = createHeadsUpManagerPhone()
-
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(notifEntry)
- assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
-
- hmp.setAnimationStateHandler(TestAnimationStateHandler())
- hmp.mOnReorderingAllowedListener.onReorderingAllowed()
- assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.isEmpty()).isTrue()
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_reorderNotAllowed_seenInShadeTrue() {
- whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
- val hmp = createHeadsUpManagerPhone()
-
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isTrue()
- }
-
- @Test
- @EnableFlags(NotificationThrottleHun.FLAG_NAME)
- fun testShowNotification_reorderAllowed_seenInShadeFalse() {
- whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
- val hmp = createHeadsUpManagerPhone()
-
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- hmp.showNotification(notifEntry)
- assertThat(notifEntry.isSeenInShade).isFalse()
- }
-
- @Test
- fun testShouldHeadsUpBecomePinned_noFSI_false() =
- testScope.runTest {
- val hum = createHeadsUpManagerPhone()
- statusBarStateController.setState(StatusBarState.KEYGUARD)
-
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
- Assert.assertFalse(hum.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
- testScope.runTest {
- val hum = createHeadsUpManagerPhone()
- statusBarStateController.setState(StatusBarState.KEYGUARD)
-
- val notifEntry =
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-
- // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
- hum.showNotification(notifEntry)
-
- val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
- headsUpEntry!!.mWasUnpinned = false
-
- Assert.assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry))
- }
-
- @Test
- fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
- testScope.runTest {
- val hum = createHeadsUpManagerPhone()
- statusBarStateController.setState(StatusBarState.KEYGUARD)
-
- val notifEntry =
- HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-
- // Add notifEntry to ANM mAlertEntries map and make it unpinned
- hum.showNotification(notifEntry)
-
- val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
- headsUpEntry!!.mWasUnpinned = true
-
- Assert.assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
- testScope.runTest {
- // GIVEN
- whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(StatusBarState.SHADE)
- runCurrent()
-
- // THEN
- Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_shadeLocked_false() =
- testScope.runTest {
- // GIVEN
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
- runCurrent()
-
- // THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
- testScope.runTest {
- // GIVEN
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(1207)
- runCurrent()
-
- // THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
- testScope.runTest {
- // GIVEN
- whenever(mBypassController.bypassEnabled).thenReturn(true)
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(StatusBarState.KEYGUARD)
- runCurrent()
-
- // THEN
- Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
- testScope.runTest {
- // GIVEN
- whenever(mBypassController.bypassEnabled).thenReturn(false)
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(StatusBarState.KEYGUARD)
- runCurrent()
-
- // THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- @Test
- fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
- testScope.runTest {
- // GIVEN
- whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
- val hmp = createHeadsUpManagerPhone()
- val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
- statusBarStateController.setState(StatusBarState.SHADE)
- runCurrent()
-
- // THEN
- Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
- }
-
- companion object {
- @get:Parameters(name = "{0}")
- val flags: List<FlagsParameterization>
- get() = buildList {
- addAll(
- FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
- .andSceneContainer()
- )
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java
deleted file mode 100644
index 2b077ed7f80d..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java
+++ /dev/null
@@ -1,162 +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.statusbar.notification.headsup;
-
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-
-import static org.mockito.Mockito.spy;
-
-import android.content.Context;
-import android.graphics.Region;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-class TestableHeadsUpManager extends HeadsUpManagerImpl {
-
- private HeadsUpEntry mLastCreatedEntry;
-
- TestableHeadsUpManager(
- Context context,
- HeadsUpManagerLogger logger,
- StatusBarStateController statusBarStateController,
- KeyguardBypassController bypassController,
- GroupMembershipManager groupMembershipManager,
- VisualStabilityProvider visualStabilityProvider,
- ConfigurationController configurationController,
- DelayableExecutor executor,
- GlobalSettings globalSettings,
- SystemClock systemClock,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger,
- JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor,
- AvalancheController avalancheController) {
- super(
- context,
- logger,
- statusBarStateController,
- bypassController,
- groupMembershipManager,
- visualStabilityProvider,
- configurationController,
- mockExecutorHandler(executor),
- globalSettings,
- systemClock,
- executor,
- accessibilityManagerWrapper,
- uiEventLogger,
- javaAdapter,
- shadeInteractor,
- avalancheController);
-
- mTouchAcceptanceDelay = HeadsUpManagerImplTest.TEST_TOUCH_ACCEPTANCE_TIME;
- mMinimumDisplayTime = HeadsUpManagerImplTest.TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = HeadsUpManagerImplTest.TEST_AUTO_DISMISS_TIME;
- mStickyForSomeTimeAutoDismissTime = HeadsUpManagerImplTest.TEST_STICKY_AUTO_DISMISS_TIME;
- }
-
- @NonNull
- @Override
- protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
- mLastCreatedEntry = spy(super.createHeadsUpEntry(entry));
- return mLastCreatedEntry;
- }
-
- // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
- @Override
- public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void addSwipedOutNotification(@NonNull String key) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void extendHeadsUp() {
- throw new UnsupportedOperationException();
- }
-
- @Nullable
- @Override
- public Region getTouchableRegion() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isHeadsUpAnimatingAwayValue() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onExpandingFinished() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
- boolean animate, @NonNull String reason) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setRemoteInputActive(@NonNull NotificationEntry entry,
- boolean remoteInputActive) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setTrackingHeadsUp(boolean tracking) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean shouldSwallowClick(@NonNull String key) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt
new file mode 100644
index 000000000000..483c2be21fc7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.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.notification.icon.ui.viewbinder
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.domain.interactor.displayWindowPropertiesInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.icon.iconManager
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class ConnectedDisplaysStatusBarNotificationIconViewStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest =
+ ConnectedDisplaysStatusBarNotificationIconViewStore(
+ TEST_DISPLAY_ID,
+ kosmos.notifCollection,
+ kosmos.iconManager,
+ kosmos.displayWindowPropertiesInteractor,
+ kosmos.notifPipeline,
+ )
+
+ private val notifCollectionListeners = mutableListOf<NotifCollectionListener>()
+
+ @Before
+ fun setupNoticCollectionListener() {
+ whenever(kosmos.notifPipeline.addCollectionListener(any())).thenAnswer { invocation ->
+ notifCollectionListeners.add(invocation.arguments[0] as NotifCollectionListener)
+ }
+ }
+
+ @Before
+ fun activate() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun iconView_unknownKey_returnsNull() =
+ kosmos.testScope.runTest {
+ val unknownKey = "unknown key"
+
+ assertThat(underTest.iconView(unknownKey)).isNull()
+ }
+
+ @Test
+ fun iconView_knownKey_returnsNonNull() =
+ kosmos.testScope.runTest {
+ val entry = createEntry()
+
+ whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+ assertThat(underTest.iconView(entry.key)).isNotNull()
+ }
+
+ @Test
+ fun iconView_knownKey_calledMultipleTimes_returnsSameInstance() =
+ kosmos.testScope.runTest {
+ val entry = createEntry()
+
+ whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+ val first = underTest.iconView(entry.key)
+ val second = underTest.iconView(entry.key)
+
+ assertThat(first).isSameInstanceAs(second)
+ }
+
+ @Test
+ fun iconView_knownKey_afterNotificationRemoved_returnsNewInstance() =
+ kosmos.testScope.runTest {
+ val entry = createEntry()
+
+ whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+ val first = underTest.iconView(entry.key)
+
+ notifCollectionListeners.forEach { it.onEntryRemoved(entry, /* reason= */ 0) }
+
+ val second = underTest.iconView(entry.key)
+
+ assertThat(first).isNotSameInstanceAs(second)
+ }
+
+ private fun createEntry(): NotificationEntry {
+ val channelId = "channelId"
+ val notificationChannel =
+ NotificationChannel(channelId, "name", NotificationManager.IMPORTANCE_DEFAULT)
+ val notification =
+ Notification.Builder(context, channelId)
+ .setContentTitle("Title")
+ .setContentText("Content text")
+ .setSmallIcon(com.android.systemui.res.R.drawable.icon)
+ .build()
+ val statusBarNotification = SbnBuilder().setNotification(notification).build()
+ val ranking =
+ RankingBuilder()
+ .setChannel(notificationChannel)
+ .setKey(statusBarNotification.key)
+ .build()
+ return NotificationEntry(
+ /* sbn = */ statusBarNotification,
+ /* ranking = */ ranking,
+ /* creationTime = */ 1234L,
+ )
+ }
+
+ private companion object {
+ const val TEST_DISPLAY_ID = 1234
+ }
+}
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 ca0f9ef5f2b0..8bca17f72c9f 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
@@ -28,7 +28,7 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.PluginManager
@@ -57,9 +57,7 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.SystemClock
-import com.android.systemui.wmshell.BubblesManager
import com.google.android.msdl.domain.MSDLPlayer
-import java.util.Optional
import junit.framework.Assert
import org.junit.After
import org.junit.Before
@@ -103,9 +101,8 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
private val gutsManager: NotificationGutsManager = mock()
private val onUserInteractionCallback: OnUserInteractionCallback = mock()
private val falsingManager: FalsingManager = mock()
- private val featureFlags: FeatureFlags = mock()
+ private val featureFlags: FeatureFlagsClassic = mock()
private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
- private val bubblesManager: BubblesManager = mock()
private val settingsController: NotificationSettingsController = mock()
private val dragController: ExpandableNotificationRowDragController = mock()
private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
@@ -147,7 +144,6 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
falsingManager,
featureFlags,
peopleNotificationIdentifier,
- Optional.of(bubblesManager),
settingsController,
dragController,
dismissibilityProvider,
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 080ac3f8c697..b323ef85b370 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
@@ -25,7 +25,6 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.util.Assert.runWithCurrentThreadAsMainThread;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -59,8 +58,8 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.TestScopeProvider;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -71,7 +70,6 @@ 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.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -79,6 +77,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
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;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -89,7 +88,6 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -99,7 +97,6 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
-import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
import kotlin.coroutines.CoroutineContext;
@@ -109,7 +106,6 @@ import kotlinx.coroutines.test.TestScope;
import org.mockito.ArgumentCaptor;
import java.util.Objects;
-import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -149,7 +145,7 @@ public class NotificationTestHelper {
private final NotificationDismissibilityProvider mDismissibilityProvider;
public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags;
- private final FakeFeatureFlags mFeatureFlags;
+ private final FakeFeatureFlagsClassic mFeatureFlags;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mRowInflaterTaskLogger;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -167,17 +163,17 @@ public class NotificationTestHelper {
Context context,
TestableDependency dependency,
@Nullable TestableLooper testLooper) {
- this(context, dependency, testLooper, new FakeFeatureFlags());
+ this(context, dependency, testLooper, new FakeFeatureFlagsClassic());
}
public NotificationTestHelper(
Context context,
TestableDependency dependency,
@Nullable TestableLooper testLooper,
- @NonNull FakeFeatureFlags featureFlags) {
+ @NonNull FakeFeatureFlagsClassic featureFlags) {
mContext = context;
mFeatureFlags = Objects.requireNonNull(featureFlags);
- dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
+ dependency.injectTestDependency(FeatureFlagsClassic.class, mFeatureFlags);
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogManager.class);
@@ -280,24 +276,6 @@ public class NotificationTestHelper {
}
/**
- * Creates a generic row with rounded border.
- *
- * @return a generic row with the set roundness.
- * @throws Exception
- */
- public ExpandableNotificationRow createRowWithRoundness(
- float topRoundness,
- float bottomRoundness,
- SourceType sourceType
- ) throws Exception {
- ExpandableNotificationRow row = createRow();
- row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
- assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
- assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
- return row;
- }
-
- /**
* Creates a generic row.
*
* @return a generic row with no special properties.
@@ -400,9 +378,8 @@ public class NotificationTestHelper {
null /* groupKey */,
makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
n.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
- ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
+ return generateRow(n, PKG, UID, USER_HANDLE,
mDefaultInflationFlags, IMPORTANCE_HIGH);
- return row;
}
@@ -668,7 +645,6 @@ public class NotificationTestHelper {
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
- Optional.of(mock(BubblesManager.class)),
mock(NotificationGutsManager.class),
mDismissibilityProvider,
mock(MetricsLogger.class),
@@ -676,7 +652,6 @@ public class NotificationTestHelper {
mock(ColorUpdateLogger.class),
mock(SmartReplyConstants.class),
mock(SmartReplyController.class),
- mFeatureFlags,
mock(IStatusBarService.class),
mock(UiEventLogger.class));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index b8745b3e95b2..2eb45902917d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -33,7 +33,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,7 +56,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private KeyguardMediaController mKeyguardMediaController;
- @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private MediaContainerController mMediaContainerController;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private SectionHeaderController mIncomingHeaderController;
@@ -73,26 +71,10 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Before
public void setUp() {
- when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer(
- invocation -> {
- int count = 2;
- if (mSectionsFeatureManager.isFilteringEnabled()) {
- count = 5;
- }
- if (mSectionsFeatureManager.isMediaControlsEnabled()) {
- if (!mSectionsFeatureManager.isFilteringEnabled()) {
- count = 5;
- } else {
- count += 1;
- }
- }
- return count;
- });
mSectionsManager =
new NotificationSectionsManager(
mConfigurationController,
mKeyguardMediaController,
- mSectionsFeatureManager,
mMediaContainerController,
mNotificationRoundnessManager,
mIncomingHeaderController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index 660eb308fdf3..87833d0c03f6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -5,7 +5,7 @@ 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.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -18,7 +18,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class NotificationTargetsHelperTest : SysuiTestCase() {
- private val featureFlags = FakeFeatureFlags()
+ private val featureFlags = FakeFeatureFlagsClassic()
lateinit var notificationTestHelper: NotificationTestHelper
private val sectionsManager: NotificationSectionsManager = mock()
private val stackScrollLayout: NotificationStackScrollLayout = mock()
@@ -90,11 +90,7 @@ class NotificationTargetsHelperTest : SysuiTestCase() {
)
val expected =
- RoundableTargets(
- before = children.attachedChildren[1],
- swiped = swiped,
- after = null,
- )
+ RoundableTargets(before = children.attachedChildren[1], swiped = swiped, after = null)
assertEquals(expected, actual)
}
}
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 2b7e95062716..dcac2941b48b 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
@@ -3,7 +3,6 @@ package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
import android.content.pm.PackageManager
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -30,7 +29,6 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.Foo
import com.android.systemui.statusbar.notification.headsup.AvalancheController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
@@ -154,22 +152,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- fun resetViewStates_defaultHun_yTranslationIsInset() {
- whenever(notificationRow.isPinned).thenReturn(true)
- whenever(notificationRow.isHeadsUp).thenReturn(true)
- resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
- }
-
- @Test
- @DisableSceneContainer
- fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
- whenever(notificationRow.isPinned).thenReturn(true)
- whenever(notificationRow.isHeadsUp).thenReturn(true)
- resetViewStates_stackMargin_changesHunYTranslation()
- }
-
- @Test
- @DisableSceneContainer
fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
@@ -183,24 +165,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
- whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
- resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
- }
-
- @Test
- @DisableSceneContainer
- @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
- whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
- resetViewStates_stackMargin_changesHunYTranslation()
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
+ fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
@@ -208,8 +173,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
+ fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
resetViewStates_stackMargin_changesHunYTranslation()
@@ -399,8 +363,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
+ fun resetViewStates_defaultHun_showingQS_hunTranslatedToMax() {
// Given: the shade is open and scrolled to the bottom to show the QuickSettings
val maxHunTranslation = 2000f
ambientState.maxHeadsUpTranslation = maxHunTranslation
@@ -416,8 +379,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
+ fun resetViewStates_hunAnimatingAway_showingQS_hunTranslatedToBottomOfScreen() {
// Given: the shade is open and scrolled to the bottom to show the QuickSettings
val bottomOfScreen = 2600f
val maxHunTranslation = 2000f
@@ -437,8 +399,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() {
+ fun resetViewStates_hunAnimatingAway_hunTranslatedToTopOfScreen() {
val topMargin = 100f
ambientState.maxHeadsUpTranslation = 2000f
ambientState.stackTopMargin = topMargin.toInt()
@@ -461,7 +422,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -472,7 +432,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -494,18 +453,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
- fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
- val topHun = mockExpandableNotificationRow()
- val bottomHun = mockExpandableNotificationRow()
- whenever(topHun.isHeadsUp).thenReturn(true)
- whenever(topHun.isPinned).thenReturn(true)
- whenever(bottomHun.isHeadsUpAnimatingAway).thenReturn(true)
-
- resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
- }
-
- @Test
@EnableSceneContainer
fun resetViewStates_emptyShadeView_isCenteredVertically_withSceneContainer() {
stackScrollAlgorithm.initView(context)
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 798465e7b165..e4dd29ad83b0 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack
-import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -24,7 +23,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
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
@@ -60,11 +58,12 @@ class StackStateAnimatorTest : SysuiTestCase() {
private val viewState: ExpandableViewState =
ExpandableViewState().apply { height = VIEW_HEIGHT }
private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
+
@Before
fun setUp() {
overrideResource(
R.dimen.go_to_full_shade_appearing_translation,
- FULL_SHADE_APPEAR_TRANSLATION
+ FULL_SHADE_APPEAR_TRANSLATION,
)
overrideResource(R.dimen.heads_up_appear_y_above_screen, HEADS_UP_ABOVE_SCREEN)
@@ -74,7 +73,6 @@ class StackStateAnimatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
val topMargin = 50f
val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
@@ -90,12 +88,11 @@ class StackStateAnimatorTest : SysuiTestCase() {
/* delay= */ 0L,
/* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
/* isHeadsUpAppear= */ true,
- /* onEndRunnable= */ null
+ /* onEndRunnable= */ null,
)
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
val screenHeight = 2000f
val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
@@ -114,12 +111,11 @@ class StackStateAnimatorTest : SysuiTestCase() {
/* delay= */ 0L,
/* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
/* isHeadsUpAppear= */ true,
- /* onEndRunnable= */ null
+ /* onEndRunnable= */ null,
)
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index f76f1ce48a5c..7f139bd69a37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -30,6 +30,8 @@ import android.content.ComponentName;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.HapticFeedbackConstants;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -45,6 +47,8 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
@@ -54,15 +58,14 @@ import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.shared.flag.DualShade;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,6 +75,8 @@ import org.mockito.stubbing.Answer;
import java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -105,6 +110,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private ActivityStarter mActivityStarter;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private KeyguardInteractor mKeyguardInteractor;
+ @Mock private QSPanelController mQSPanelController;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -150,6 +156,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
.thenAnswer((Answer<Integer>) invocation -> invocation.getArgument(0));
+ when(mCentralSurfaces.getQSPanelController()).thenReturn(mQSPanelController);
}
@Test
@@ -230,4 +237,45 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
verify(mQSHost).addTile(c, false);
}
+
+ @Test
+ @DisableFlags(value = {QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME})
+ public void clickQsTile_flagsDisabled_callsQSPanelController() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.clickTile(c);
+ verify(mQSPanelController).clickTile(c);
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ public void clickQsTile_onlyQSComposeFlag_callsQSHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.clickTile(c);
+ verify(mQSPanelController, never()).clickTile(c);
+ verify(mQSHost).clickTile(c);
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ @DisableFlags(QSComposeFragment.FLAG_NAME)
+ public void clickQsTile_onlyDualShadeFlag_callsQSHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.clickTile(c);
+ verify(mQSPanelController, never()).clickTile(c);
+ verify(mQSHost).clickTile(c);
+ }
+
+ @Test
+ @EnableFlags(value = {QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME})
+ public void clickQsTile_qsComposeAndDualShadeFlags_callsQSHost() {
+ ComponentName c = new ComponentName("testpkg", "testcls");
+
+ mSbcqCallbacks.clickTile(c);
+ verify(mQSPanelController, never()).clickTile(c);
+ verify(mQSHost).clickTile(c);
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
deleted file mode 100644
index f9f2cd328094..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.platform.test.annotations.DisableFlags;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.TextView;
-
-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.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeHeadsUpTracker;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
-import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
-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.stack.NotificationRoundnessManager;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
-
- private final NotificationStackScrollLayoutController mStackScrollerController =
- mock(NotificationStackScrollLayoutController.class);
- private final ShadeViewController mShadeViewController =
- mock(ShadeViewController.class);
- private final ShadeHeadsUpTracker mShadeHeadsUpTracker = mock(ShadeHeadsUpTracker.class);
- private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
- private HeadsUpAppearanceController mHeadsUpAppearanceController;
- private NotificationTestHelper mTestHelper;
- private ExpandableNotificationRow mRow;
- private NotificationEntry mEntry;
- private HeadsUpStatusBarView mHeadsUpStatusBarView;
- private HeadsUpManager mHeadsUpManager;
- private View mOperatorNameView;
- private StatusBarStateController mStatusbarStateController;
- private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
- private KeyguardBypassController mBypassController;
- private NotificationWakeUpCoordinator mWakeUpCoordinator;
- private KeyguardStateController mKeyguardStateController;
- private CommandQueue mCommandQueue;
- private NotificationRoundnessManager mNotificationRoundnessManager;
- private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- mTestHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = mTestHelper.createRow();
- mEntry = mRow.getEntry();
- mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
- mock(TextView.class));
- mHeadsUpManager = mock(HeadsUpManager.class);
- mOperatorNameView = new View(mContext);
- mStatusbarStateController = mock(StatusBarStateController.class);
- mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
- mBypassController = mock(KeyguardBypassController.class);
- mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
- mKeyguardStateController = mock(KeyguardStateController.class);
- mCommandQueue = mock(CommandQueue.class);
- mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
- when(mShadeViewController.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker);
- mHeadsUpAppearanceController = new HeadsUpAppearanceController(
- mHeadsUpManager,
- mStatusbarStateController,
- mPhoneStatusBarTransitions,
- mBypassController,
- mWakeUpCoordinator,
- mDarkIconDispatcher,
- mKeyguardStateController,
- mCommandQueue,
- mStackScrollerController,
- mShadeViewController,
- mNotificationRoundnessManager,
- mHeadsUpStatusBarView,
- new Clock(mContext, null),
- mFeatureFlags,
- mock(HeadsUpNotificationIconInteractor.class),
- Optional.of(mOperatorNameView));
- mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
- }
-
- @Test
- public void testShowinEntryUpdated() {
- mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
- mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
- assertEquals(mRow.getEntry(), mHeadsUpStatusBarView.getShowingEntry());
-
- mRow.setPinnedStatus(PinnedStatus.NotPinned);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
- assertEquals(null, mHeadsUpStatusBarView.getShowingEntry());
- }
-
- @Test
- public void testShownUpdated() {
- mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
- mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
- assertTrue(mHeadsUpAppearanceController.isShown());
-
- mRow.setPinnedStatus(PinnedStatus.NotPinned);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
- Assert.assertFalse(mHeadsUpAppearanceController.isShown());
- }
-
- @Test
- @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
- public void testHeaderUpdated() {
- mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
- mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
- assertEquals(mRow.getHeaderVisibleAmount(), 0.0f, 0.0f);
-
- mRow.setPinnedStatus(PinnedStatus.NotPinned);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
- assertEquals(mRow.getHeaderVisibleAmount(), 1.0f, 0.0f);
- }
-
- @Test
- public void testOperatorNameViewUpdated() {
- mHeadsUpAppearanceController.setAnimationsEnabled(false);
-
- mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
- mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
- assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility());
-
- mRow.setPinnedStatus(PinnedStatus.NotPinned);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
- assertEquals(View.VISIBLE, mOperatorNameView.getVisibility());
- }
-
- @Test
- public void constructor_animationValuesUpdated() {
- float appearFraction = .75f;
- float expandedHeight = 400f;
- when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction);
- when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight);
-
- HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
- mHeadsUpManager,
- mStatusbarStateController,
- mPhoneStatusBarTransitions,
- mBypassController,
- mWakeUpCoordinator,
- mDarkIconDispatcher,
- mKeyguardStateController,
- mCommandQueue,
- mStackScrollerController,
- mShadeViewController,
- mNotificationRoundnessManager,
- mHeadsUpStatusBarView,
- new Clock(mContext, null),
- mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
- Optional.empty());
-
- assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
- assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
- }
-
- @Test
- public void testDestroy() {
- reset(mHeadsUpManager);
- reset(mDarkIconDispatcher);
- reset(mShadeHeadsUpTracker);
- reset(mStackScrollerController);
-
- mHeadsUpAppearanceController.onViewDetached();
-
- verify(mHeadsUpManager).removeListener(any());
- verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any());
- verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(isNull());
- verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
- }
-
- @Test
- public void testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() {
- // Pulsing: Enable flag and dozing
- when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
- when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
-
- // Pulsing: Enabled
- mRow.setHeadsUp(true);
- mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
-
- String debugString = mRow.getRoundableState().debugString();
- assertEquals(
- "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
- /* expected = */ 1,
- /* actual = */ mRow.getTopRoundness(),
- /* delta = */ 0.001
- );
- assertTrue(debugString.contains("Pulsing"));
-
- // Pulsing: Disabled
- mRow.setHeadsUp(false);
- mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
-
- assertEquals(
- "If Pulsing is disabled, roundness should be set to 0. Value: "
- + mRow.getRoundableState().debugString(),
- /* expected = */ 0,
- /* actual = */ mRow.getTopRoundness(),
- /* delta = */ 0.001
- );
- }
-
- @Test
- public void testPulsingRoundness_onHeadsUpStateChanged() {
- // Pulsing: Enable flag and dozing
- when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
- when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
-
- // Pulsing: Enabled
- mEntry.setHeadsUp(true);
- mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
-
- String debugString = mRow.getRoundableState().debugString();
- assertEquals(
- "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
- /* expected = */ 1,
- /* actual = */ mRow.getTopRoundness(),
- /* delta = */ 0.001
- );
- assertTrue(debugString.contains("Pulsing"));
-
- // Pulsing: Disabled
- mEntry.setHeadsUp(false);
- mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
-
- assertEquals(
- "If Pulsing is disabled, roundness should be set to 0. Value: "
- + mRow.getRoundableState().debugString(),
- /* expected = */ 0,
- /* actual = */ mRow.getTopRoundness(),
- /* delta = */ 0.001
- );
- }
-
- @Test
- public void onHeadsUpStateChanged_true_transitionsNotified() {
- mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
-
- verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
- }
-
- @Test
- public void onHeadsUpStateChanged_false_transitionsNotified() {
- mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
-
- verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
new file mode 100644
index 000000000000..a9afb0658c50
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.phone
+
+import android.platform.test.annotations.DisableFlags
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.plugins.fakeDarkIconDispatcher
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.ShadeHeadsUpTracker
+import com.android.systemui.shade.shadeViewController
+import com.android.systemui.statusbar.HeadsUpStatusBarView
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
+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.stack.NotificationRoundnessManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class HeadsUpAppearanceControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val stackScrollerController = mock<NotificationStackScrollLayoutController>()
+ private val shadeViewController = kosmos.shadeViewController
+ private val shadeHeadsUpTracker = mock<ShadeHeadsUpTracker>()
+ private val darkIconDispatcher = kosmos.fakeDarkIconDispatcher
+ private val statusBarStateController = kosmos.statusBarStateController
+ private val phoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
+ private val bypassController = kosmos.keyguardBypassController
+ private val wakeUpCoordinator = kosmos.notificationWakeUpCoordinator
+ private val keyguardStateController = kosmos.keyguardStateController
+ private val commandQueue = kosmos.commandQueue
+ private val notificationRoundnessManager = mock<NotificationRoundnessManager>()
+ private var headsUpManager = kosmos.headsUpManager
+
+ private lateinit var testHelper: NotificationTestHelper
+ private lateinit var row: ExpandableNotificationRow
+ private lateinit var entry: NotificationEntry
+ private lateinit var headsUpStatusBarView: HeadsUpStatusBarView
+ private lateinit var operatorNameView: View
+
+ private lateinit var underTest: HeadsUpAppearanceController
+
+ @Before
+ @Throws(Exception::class)
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ row = testHelper.createRow()
+ entry = row.entry
+ headsUpStatusBarView = HeadsUpStatusBarView(mContext, mock<View>(), mock<TextView>())
+ operatorNameView = View(mContext)
+
+ whenever(shadeViewController.shadeHeadsUpTracker).thenReturn(shadeHeadsUpTracker)
+ underTest =
+ HeadsUpAppearanceController(
+ headsUpManager,
+ statusBarStateController,
+ phoneStatusBarTransitions,
+ bypassController,
+ wakeUpCoordinator,
+ darkIconDispatcher,
+ keyguardStateController,
+ commandQueue,
+ stackScrollerController,
+ shadeViewController,
+ notificationRoundnessManager,
+ headsUpStatusBarView,
+ Clock(mContext, null),
+ kosmos.headsUpNotificationIconInteractor,
+ Optional.of(operatorNameView),
+ )
+ underTest.setAppearFraction(0.0f, 0.0f)
+ }
+
+ @Test
+ fun testShowinEntryUpdated() {
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(headsUpManager.getTopEntry()).thenReturn(entry)
+ underTest.onHeadsUpPinned(entry)
+ assertThat(headsUpStatusBarView.showingEntry).isEqualTo(row.entry)
+
+ row.setPinnedStatus(PinnedStatus.NotPinned)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.onHeadsUpUnPinned(entry)
+ assertThat(headsUpStatusBarView.showingEntry).isNull()
+ }
+
+ @Test
+ fun testPinnedStatusUpdated() {
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(headsUpManager.getTopEntry()).thenReturn(entry)
+ underTest.onHeadsUpPinned(entry)
+ assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.PinnedBySystem)
+
+ row.setPinnedStatus(PinnedStatus.NotPinned)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.onHeadsUpUnPinned(entry)
+ assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.NotPinned)
+ }
+
+ @Test
+ @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+ fun testHeaderUpdated() {
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(headsUpManager.getTopEntry()).thenReturn(entry)
+ underTest.onHeadsUpPinned(entry)
+ assertThat(row.headerVisibleAmount).isEqualTo(0.0f)
+
+ row.setPinnedStatus(PinnedStatus.NotPinned)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.onHeadsUpUnPinned(entry)
+ assertThat(row.headerVisibleAmount).isEqualTo(1.0f)
+ }
+
+ @Test
+ fun testOperatorNameViewUpdated() {
+ underTest.setAnimationsEnabled(false)
+
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(headsUpManager.getTopEntry()).thenReturn(entry)
+ underTest.onHeadsUpPinned(entry)
+ assertThat(operatorNameView.visibility).isEqualTo(View.INVISIBLE)
+
+ row.setPinnedStatus(PinnedStatus.NotPinned)
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.onHeadsUpUnPinned(entry)
+ assertThat(operatorNameView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun constructor_animationValuesUpdated() {
+ val appearFraction = .75f
+ val expandedHeight = 400f
+ whenever(stackScrollerController.appearFraction).thenReturn(appearFraction)
+ whenever(stackScrollerController.expandedHeight).thenReturn(expandedHeight)
+
+ val newController =
+ HeadsUpAppearanceController(
+ headsUpManager,
+ statusBarStateController,
+ phoneStatusBarTransitions,
+ bypassController,
+ wakeUpCoordinator,
+ darkIconDispatcher,
+ keyguardStateController,
+ commandQueue,
+ stackScrollerController,
+ shadeViewController,
+ notificationRoundnessManager,
+ headsUpStatusBarView,
+ Clock(mContext, null),
+ mock<HeadsUpNotificationIconInteractor>(),
+ Optional.empty(),
+ )
+
+ assertThat(newController.mExpandedHeight).isEqualTo(expandedHeight)
+ assertThat(newController.mAppearFraction).isEqualTo(appearFraction)
+ }
+
+ @Test
+ fun testDestroy() {
+ reset(headsUpManager)
+ reset(shadeHeadsUpTracker)
+ reset(stackScrollerController)
+
+ underTest.onViewDetached()
+
+ verify(headsUpManager).removeListener(any())
+ assertThat(darkIconDispatcher.receivers).isEmpty()
+ verify(shadeHeadsUpTracker).removeTrackingHeadsUpListener(any())
+ verify(shadeHeadsUpTracker).setHeadsUpAppearanceController(null)
+ verify(stackScrollerController).removeOnExpandedHeightChangedListener(any())
+ }
+
+ @Test
+ fun testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() {
+ // Pulsing: Enable flag and dozing
+ whenever(notificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true)
+ whenever(testHelper.statusBarStateController.isDozing).thenReturn(true)
+
+ // Pulsing: Enabled
+ row.isHeadsUp = true
+ underTest.updateHeadsUpAndPulsingRoundness(entry)
+
+ val debugString: String = row.roundableState.debugString()
+ // If Pulsing is enabled, roundness should be set to 1
+ assertThat(row.topRoundness.toDouble()).isWithin(0.001).of(1.0)
+ assertThat(debugString).contains("Pulsing")
+
+ // Pulsing: Disabled
+ row.isHeadsUp = false
+ underTest.updateHeadsUpAndPulsingRoundness(entry)
+
+ // If Pulsing is disabled, roundness should be set to 0
+ assertThat(row.topRoundness.toDouble()).isWithin(0.001).of(0.0)
+ }
+
+ @Test
+ fun testPulsingRoundness_onHeadsUpStateChanged() {
+ // Pulsing: Enable flag and dozing
+ whenever(notificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true)
+ whenever(testHelper.statusBarStateController.isDozing).thenReturn(true)
+
+ // Pulsing: Enabled
+ entry.setHeadsUp(true)
+ underTest.onHeadsUpStateChanged(entry, true)
+
+ val debugString: String = row.roundableState.debugString()
+ // If Pulsing is enabled, roundness should be set to 1
+ assertThat(row.topRoundness.toDouble()).isWithin(0.001).of(1.0)
+ assertThat(debugString).contains("Pulsing")
+
+ // Pulsing: Disabled
+ entry.setHeadsUp(false)
+ underTest.onHeadsUpStateChanged(entry, false)
+
+ // If Pulsing is disabled, roundness should be set to 0
+ assertThat(row.topRoundness.toDouble()).isWithin(0.001).of(0.0)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_transitionsNotified() {
+ underTest.onHeadsUpStateChanged(entry, true)
+
+ verify(phoneStatusBarTransitions).onHeadsUpStateChanged(true)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_transitionsNotified() {
+ underTest.onHeadsUpStateChanged(entry, false)
+
+ verify(phoneStatusBarTransitions).onHeadsUpStateChanged(false)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0eb620343e6a..d174484219ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -190,6 +190,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+ @Mock
+ private KeyguardDismissActionInteractor mKeyguardDismissActionInteractor;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -236,7 +238,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mKeyguardTransitionInteractor,
mock(KeyguardDismissTransitionInteractor.class),
StandardTestDispatcher(null, null),
- () -> mock(KeyguardDismissActionInteractor.class),
+ () -> mKeyguardDismissActionInteractor,
mSelectedUserInteractor,
mock(JavaAdapter.class),
() -> mSceneInteractor,
@@ -968,4 +970,33 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
);
verify(mAlternateBouncerInteractor).hide();
}
+
+ @Test
+ public void hideAlternateBouncer_clearsDismissActionByDefault() {
+ clearInvocations(mKeyguardDismissActionInteractor);
+
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(/* updateScrim= */ true);
+
+ verify(mKeyguardDismissActionInteractor).clearDismissAction();
+ }
+
+ @Test
+ public void hideAlternateBouncer_clearsDismissActionExplicitly() {
+ clearInvocations(mKeyguardDismissActionInteractor);
+
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(
+ /* updateScrim= */ true, /* clearDismissAction= */ true);
+
+ verify(mKeyguardDismissActionInteractor).clearDismissAction();
+ }
+
+ @Test
+ public void hideAlternateBouncer_doNotClearDismissActionExplicitly() {
+ clearInvocations(mKeyguardDismissActionInteractor);
+
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(
+ /* updateScrim= */ true, /* clearDismissAction= */ false);
+
+ verify(mKeyguardDismissActionInteractor, never()).clearDismissAction();
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index 110dec6c33aa..f76ee5e3ebc9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -87,7 +87,7 @@ private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUN
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
-@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, StatusBarChipsModernization.FLAG_NAME)
class OngoingCallControllerViaListenerTest : SysuiTestCase() {
private val kosmos = Kosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
index 2ad50cc38b7c..647b5f86fcee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
@@ -29,6 +29,7 @@ import android.widget.LinearLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.SysuiTestCase
@@ -77,6 +78,7 @@ import org.mockito.kotlin.whenever
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+@DisableFlags(StatusBarChipsModernization.FLAG_NAME)
class OngoingCallControllerViaRepoTest : SysuiTestCase() {
private val kosmos = Kosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 4c6eaa589e6a..a44631348796 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.phone.ongoingcall.data.repository
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.google.common.truth.Truth.assertThat
@@ -28,6 +31,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@DisableFlags(StatusBarChipsModernization.FLAG_NAME)
class OngoingCallRepositoryTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val underTest = kosmos.ongoingCallRepository
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
new file mode 100644
index 000000000000..ca1413e48966
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor
+
+import android.app.PendingIntent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+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.statusbar.StatusBarIconView
+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.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OngoingCallInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos().useUnconfinedTestDispatcher()
+ private val repository = kosmos.activeNotificationListRepository
+ private val underTest = kosmos.ongoingCallInteractor
+
+ @Test
+ fun noNotification_emitsNoCall() = runTest {
+ val state by collectLastValue(underTest.ongoingCallState)
+ assertThat(state).isInstanceOf(OngoingCallModel.NoCall::class.java)
+ }
+
+ @Test
+ fun ongoingCallNotification_setsNotificationIconAndIntent() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ // Set up notification with icon view and intent
+ val testIconView: StatusBarIconView = mock()
+ val testIntent: PendingIntent = mock()
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = 1000L,
+ callType = CallType.Ongoing,
+ statusBarChipIcon = testIconView,
+ contentIntent = testIntent
+ )
+ )
+ }
+ .build()
+
+ // Verify model is InCall and has the correct icon and intent.
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ val model = latest as OngoingCallModel.InCall
+ assertThat(model.notificationIconView).isSameInstanceAs(testIconView)
+ assertThat(model.intent).isSameInstanceAs(testIntent)
+ }
+
+ @Test
+ fun ongoingCallNotification_emitsInCall() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
+ )
+ )
+ }
+ .build()
+
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ }
+
+ @Test
+ fun notificationRemoved_emitsNoCall() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
+ )
+ )
+ }
+ .build()
+
+ repository.activeNotifications.value = ActiveNotificationsStore()
+ assertThat(latest).isInstanceOf(OngoingCallModel.NoCall::class.java)
+ }
+
+ @Test
+ fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() =
+ kosmos.runTest {
+ kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = 1000L,
+ callType = CallType.Ongoing,
+ uid = UID
+ )
+ )
+ }
+ .build()
+
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+ }
+
+ @Test
+ fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() =
+ kosmos.runTest {
+ kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = 1000L,
+ callType = CallType.Ongoing,
+ uid = UID
+ )
+ )
+ }
+ .build()
+
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ }
+
+ @Test
+ fun ongoingCallNotification_visibilityChanges_updatesState() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingCallState)
+
+ // Start with notification and app not visible
+ kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = 1000L,
+ callType = CallType.Ongoing,
+ uid = UID
+ )
+ )
+ }
+ .build()
+ assertThat(latest)
+ .isInstanceOf(OngoingCallModel.InCall::class.java)
+
+ // App becomes visible
+ kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+
+ // App becomes invisible again
+ kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false)
+ assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+ }
+
+ companion object {
+ private const val UID = 885
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index 9888574071e6..a2ca12c13a3e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
@@ -30,6 +30,7 @@ class FakeHomeStatusBarViewBinder : HomeStatusBarViewBinder {
var listener: StatusBarVisibilityChangeListener? = null
override fun bind(
+ displayId: Int,
view: View,
viewModel: HomeStatusBarViewModel,
systemEventChipAnimateIn: ((View) -> Unit)?,
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 b9cdd06de5a2..7b04fc827d83 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
@@ -28,8 +28,6 @@ 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.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -39,7 +37,9 @@ 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.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.assertLogsWtf
@@ -52,6 +52,7 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository
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
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
@@ -66,15 +67,17 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
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.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -83,35 +86,22 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class HomeStatusBarViewModelImplTest : SysuiTestCase() {
- private val kosmos =
- Kosmos().also {
- it.testCase = this
- it.testDispatcher = UnconfinedTestDispatcher()
- }
-
- private val testScope = kosmos.testScope
-
- private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
- private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
- private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository
-
- private lateinit var underTest: HomeStatusBarViewModel
+ private val kosmos by lazy {
+ testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
+ }
+ private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
- // Initialize here because some flags are checked when this class is constructed
- underTest = kosmos.homeStatusBarViewModel
}
@Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -125,10 +115,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isTransitioningFromLockscreenToOccluded_running_isTrue() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -142,10 +132,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isTransitioningFromLockscreenToOccluded_finished_isFalse() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
testScope.testScheduler,
@@ -156,10 +146,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -173,10 +163,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.AOD,
KeyguardState.LOCKSCREEN,
@@ -190,10 +180,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -205,7 +195,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
assertThat(latest).isTrue()
// WHEN the repo updates the transition to finished
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -220,10 +210,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun transitionFromLockscreenToDreamStartedEvent_started_emitted() =
- testScope.runTest {
+ kosmos.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -237,10 +227,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() =
- testScope.runTest {
+ kosmos.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -249,7 +239,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -258,7 +248,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -272,10 +262,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() =
- testScope.runTest {
+ kosmos.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -287,7 +277,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
// WHEN the transition progresses through its animation by going through the RUNNING
// step with increasing fractions
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -296,7 +286,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -305,7 +295,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -321,10 +311,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() =
- testScope.runTest {
+ kosmos.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.OCCLUDED,
@@ -338,10 +328,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() =
- testScope.runTest {
+ kosmos.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
- keyguardTransitionRepository.sendTransitionStep(
+ fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
KeyguardState.LOCKSCREEN,
KeyguardState.DREAMING,
@@ -359,8 +349,8 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
fun areNotificationsLightsOut_lowProfileWithNotifications_true() =
- testScope.runTest {
- statusBarModeRepository.defaultDisplay.statusBarMode.value =
+ kosmos.runTest {
+ fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
StatusBarMode.LIGHTS_OUT_TRANSPARENT
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
@@ -373,8 +363,8 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() =
- testScope.runTest {
- statusBarModeRepository.defaultDisplay.statusBarMode.value =
+ kosmos.runTest {
+ fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
StatusBarMode.LIGHTS_OUT_TRANSPARENT
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
@@ -387,8 +377,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() =
- testScope.runTest {
- statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+ kosmos.runTest {
+ fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
+ StatusBarMode.TRANSPARENT
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(emptyList())
@@ -400,8 +391,9 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() =
- testScope.runTest {
- statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+ kosmos.runTest {
+ fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
+ StatusBarMode.TRANSPARENT
activeNotificationListRepository.activeNotifications.value =
activeNotificationsStore(testNotifications)
@@ -413,7 +405,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
fun areNotificationsLightsOut_requiresFlagEnabled() =
- testScope.runTest {
+ kosmos.runTest {
assertLogsWtf {
val flow = underTest.areNotificationsLightsOut(DISPLAY_ID)
assertThat(flow).isEqualTo(emptyFlow<Boolean>())
@@ -422,7 +414,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun primaryOngoingActivityChip_matchesViewModel() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.primaryOngoingActivityChip)
kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
@@ -441,7 +433,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneLockscreen_notOccluded_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
@@ -452,7 +444,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneLockscreen_occluded_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
@@ -463,7 +455,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneBouncer_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
@@ -473,7 +465,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneCommunal_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Communal)
@@ -483,7 +475,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneShade_false() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
@@ -493,7 +485,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isHomeStatusBarAllowedByScene_sceneGone_true() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
@@ -503,35 +495,91 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isClockVisible_allowedByDisableFlags_visible() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
}
@Test
- fun isClockVisible_notAllowedByDisableFlags_gone() =
- testScope.runTest {
+ fun isClockVisible_notAllowedByDisableFlags_invisible() =
+ kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE)
- assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun isClockVisible_allowedByFlags_hunActive_notVisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isClockVisible)
+ transitionKeyguardToGone()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+ // there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository(isPinned = true)
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun isClockVisible_allowedByFlags_hunBecomesInactive_visibleAgain() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isClockVisible)
+ transitionKeyguardToGone()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+ // there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository(isPinned = true)
+ )
+
+ // hun goes away
+ headsUpNotificationRepository.setNotifications(listOf())
+
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun isClockVisible_disabledByFlags_hunBecomesInactive_neverVisible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isClockVisible)
+ transitionKeyguardToGone()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE)
+ // there is an active HUN
+ headsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository(isPinned = true)
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+
+ // hun goes away
+ headsUpNotificationRepository.setNotifications(listOf())
+
+ assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
}
@Test
fun isNotificationIconContainerVisible_allowedByDisableFlags_visible() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
@@ -539,23 +587,55 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isNotificationIconContainerVisible_notAllowedByDisableFlags_gone() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE)
assertThat(latest!!.visibility).isEqualTo(View.GONE)
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOn() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOff() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
fun isSystemInfoVisible_allowedByDisableFlags_visible() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
@@ -563,11 +643,11 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
- disableFlagsRepository.disableFlags.value =
+ fakeDisableFlagsRepository.disableFlags.value =
DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)
assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE)
@@ -575,7 +655,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
fun systemInfoCombineVis_animationsPassThrough() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.systemInfoCombinedVis)
transitionKeyguardToGone()
@@ -601,18 +681,18 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun lockscreenVisible_sceneFlagOff_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- testScope = this,
+ testScope = testScope,
)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -620,14 +700,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun lockscreenVisible_sceneFlagOn_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -635,18 +715,18 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun bouncerVisible_sceneFlagOff_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
- testScope = this,
+ testScope = testScope,
)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -654,14 +734,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun bouncerVisible_sceneFlagOn_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -669,15 +749,15 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun keyguardIsOccluded_sceneFlagOff_statusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
- testScope = this,
+ testScope = testScope,
)
assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
@@ -688,7 +768,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun keyguardIsOccluded_sceneFlagOn_statusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -704,7 +784,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun keyguardNotShown_sceneFlagOff_statusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -719,7 +799,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun shadeNotShown_sceneFlagOff_statusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -735,7 +815,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun keyguardNotShownAndShadeNotShown_sceneFlagOn_statusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -750,7 +830,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun shadeShown_sceneFlagOff_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -758,7 +838,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
kosmos.shadeTestUtil.setShadeExpansion(1f)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -766,7 +846,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun shadeShown_sceneFlagOn_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -774,7 +854,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -782,20 +862,20 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@DisableSceneContainer
fun secureCameraActive_sceneFlagOff_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
// Secure camera is an occluding activity
- keyguardTransitionRepository.sendTransitionSteps(
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
- testScope = this,
+ testScope = testScope,
)
kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
@@ -803,7 +883,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@Test
@EnableSceneContainer
fun secureCameraActive_sceneFlagOn_noStatusBarViewsShown() =
- testScope.runTest {
+ kosmos.runTest {
val clockVisible by collectLastValue(underTest.isClockVisible)
val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -813,21 +893,26 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
}
+ // Cribbed from [HeadsUpNotificationInteractorTest.kt]
+ private fun fakeHeadsUpRowRepository(key: String = "test key", isPinned: Boolean = false) =
+ FakeHeadsUpRowRepository(key = key, isPinned = isPinned)
+
private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
ActiveNotificationsStore.Builder()
.apply { notifications.forEach(::addIndividualNotif) }
.build()
- private val testNotifications =
+ private val testNotifications by lazy {
listOf(activeNotificationModel(key = "notif1"), activeNotificationModel(key = "notif2"))
+ }
- private suspend fun transitionKeyguardToGone() {
- keyguardTransitionRepository.sendTransitionSteps(
+ private suspend fun Kosmos.transitionKeyguardToGone() {
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
testScope = testScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 4eef308a2772..f8d45e456d94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -1248,10 +1248,12 @@ class WifiRepositoryImplTest : SysuiTestCase() {
otherUserMockContext,
)
userRepository.setSelectedUserInfo(ANOTHER_USER)
+ verify(wifiPickerTracker).onStop()
// THEN we use the different user's context to create WifiPickerTracker
val newCaptor = argumentCaptor<Context>()
verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any())
+ verify(wifiPickerTracker).onStop()
assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext)
}
@@ -1288,6 +1290,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
// THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off
verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any())
+ verify(wifiPickerTracker, never()).onStop()
}
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 74d4178891b9..3d6882c3fdaf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -251,6 +251,22 @@ class ZenModeInteractorTest : SysuiTestCase() {
}
@Test
+ fun deactivateAllModes_updatesCorrectModes() =
+ testScope.runTest {
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Inactive").setActive(false).build(),
+ TestModeBuilder().setName("Active").setActive(true).build(),
+ )
+ )
+
+ underTest.deactivateAllModes()
+
+ assertThat(zenModeRepository.getModes().filter { it.isActive }).isEmpty()
+ }
+
+ @Test
fun activeModes_computesMainActiveMode() =
testScope.runTest {
val activeModes by collectLastValue(underTest.activeModes)
@@ -378,8 +394,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
assertThat(dndMode!!.isActive).isFalse()
- zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
- zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
runCurrent()
assertThat(dndMode!!.isActive).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
index 32ef501cd9e3..09ea767d6efc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
@@ -90,15 +90,15 @@ class GlowBoxEffectTest : SysuiTestCase() {
assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_IN)
- animatorTestRule.advanceTimeBy(config.easeInDuration + 50L)
+ animatorTestRule.advanceAnimationDuration(config.easeInDuration + 50L)
assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.MAIN)
- animatorTestRule.advanceTimeBy(config.duration + 50L)
+ animatorTestRule.advanceAnimationDuration(config.duration + 50L)
assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT)
- animatorTestRule.advanceTimeBy(config.easeOutDuration + 50L)
+ animatorTestRule.advanceAnimationDuration(config.easeOutDuration + 50L)
assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 67a42196d2e2..c9be7e3d0392 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -117,9 +117,9 @@ class LoadingEffectTest : SysuiTestCase() {
loadingEffect.play()
// Execute all the animators by advancing each duration with some buffer.
- animatorTestRule.advanceTimeBy(config.easeInDuration.toLong())
- animatorTestRule.advanceTimeBy(config.maxDuration.toLong())
- animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
+ animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong())
+ animatorTestRule.advanceAnimationDuration(config.maxDuration.toLong())
+ animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong())
animatorTestRule.advanceTimeBy(500)
assertThat(states)
@@ -206,12 +206,12 @@ class LoadingEffectTest : SysuiTestCase() {
assertThat(isFinished).isFalse()
loadingEffect.play()
- animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L)
+ animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong() + 500L)
assertThat(isFinished).isFalse()
loadingEffect.finish()
- animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L)
+ animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong() + 500L)
assertThat(isFinished).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index e7ca1dd3e4b7..7b52dd836b51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -434,13 +434,13 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
@Test
public void onSettingChanged_honorThemeStyle() {
when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
- List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
- Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
- for (Style style : validStyles) {
+ @Style.Type List<Integer> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
+ Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
+ for (@Style.Type int style : validStyles) {
reset(mSecureSettings);
String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\","
- + "\"android.theme.customization.theme_style\":\"" + style.name() + "\"}";
+ + "\"android.theme.customization.theme_style\":\"" + Style.name(style) + "\"}";
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
index 7d3ed92cecc6..7aa389a1739f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
@@ -20,10 +20,12 @@ import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -33,12 +35,24 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HomeGestureRecognizerTest : SysuiTestCase() {
+ companion object {
+ const val THRESHOLD_VELOCITY_PX_PER_MS = 1f
+ const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS - 0.01f
+ const val FAST = THRESHOLD_VELOCITY_PX_PER_MS + 0.01f
+ }
+
private var gestureState: GestureState = GestureState.NotStarted
+ private var velocityTracker = testKosmos().fakeVelocityTracker
private val gestureRecognizer =
- HomeGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+ HomeGestureRecognizer(
+ gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+ velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
+ velocityTracker = velocityTracker,
+ )
@Before
fun before() {
+ velocityTracker.setVelocity(Velocity(FAST))
gestureRecognizer.addGestureStateCallback { gestureState = it }
}
@@ -48,6 +62,12 @@ class HomeGestureRecognizerTest : SysuiTestCase() {
}
@Test
+ fun doesntTriggerGestureFinished_onGestureSpeedTooSlow() {
+ velocityTracker.setVelocity(Velocity(SLOW))
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
+ }
+
+ @Test
fun triggersGestureProgressForThreeFingerGestureStarted() {
assertStateAfterEvents(
events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
index c5c0d59ea48b..cb74e6569b3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
@@ -17,21 +17,19 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -39,26 +37,23 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() {
companion object {
const val THRESHOLD_VELOCITY_PX_PER_MS = 0.1f
- // multiply by 1000 to get px/ms instead of px/s which is unit used by velocity tracker
- const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS * 1000 - 1
- const val FAST = THRESHOLD_VELOCITY_PX_PER_MS * 1000 + 1
+ const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS - 0.01f
+ const val FAST = THRESHOLD_VELOCITY_PX_PER_MS + 0.01f
}
+ private var velocityTracker = testKosmos().fakeVelocityTracker
+
private var gestureState: GestureState = GestureState.NotStarted
- private val velocityTracker1D =
- mock<VelocityTracker1D> {
- // by default return correct speed for the gesture - as if pointer is slowing down
- on { calculateVelocity() } doReturn SLOW
- }
private val gestureRecognizer =
RecentAppsGestureRecognizer(
gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
- velocityTracker = VerticalVelocityTracker(velocityTracker1D),
+ velocityTracker = velocityTracker,
)
@Before
fun before() {
+ velocityTracker.setVelocity(Velocity(SLOW))
gestureRecognizer.addGestureStateCallback { gestureState = it }
}
@@ -69,7 +64,7 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() {
@Test
fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
- whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST)
+ velocityTracker.setVelocity(Velocity(FAST))
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
index 266a60dfb6e9..665f55bfc2ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
@@ -193,7 +193,11 @@ class FoldLightRevealOverlayAnimationTest : SysuiTestCase() {
}
private fun TestScope.advanceTime(timeMs: Long) {
- animatorTestRule.advanceTimeBy(timeMs)
+ if (timeMs == ANIMATION_DURATION) {
+ animatorTestRule.advanceAnimationDuration(timeMs)
+ } else {
+ animatorTestRule.advanceTimeBy(timeMs)
+ }
advanceTimeBy(timeMs)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index ef2d4ce2becc..c7b685fba455 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -182,7 +182,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
private fun runOnProgressThreadWithInterval(
vararg blocks: () -> Unit,
- intervalMillis: Long = 60,
+ intervalMillis: Long = 100,
) {
blocks.forEach {
bgHandler.post(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
new file mode 100644
index 000000000000..8acf53869774
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+import android.media.AudioManager
+import android.testing.TestableLooper
+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.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSafetyWarningInteractorTest : SysuiTestCase() {
+
+ private val kosmos: Kosmos = testKosmos()
+
+ private lateinit var underTest: VolumeDialogSafetyWarningInteractor
+
+ @Before
+ fun setup() {
+ kosmos.useUnconfinedTestDispatcher()
+ underTest = kosmos.volumeDialogSafetyWarningInteractor
+ }
+
+ @Test
+ fun dismiss_isShowingSafetyWarning_isFalse() =
+ with(kosmos) {
+ runTest {
+ val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+ underTest.onSafetyWarningDismissed()
+
+ assertThat(isShowingSafetyWarning).isFalse()
+ }
+ }
+
+ @Test
+ fun flagShowUi_isShowingSafetyWarning_isTrue() =
+ with(kosmos) {
+ runTest {
+ val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+ fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI)
+
+ assertThat(isShowingSafetyWarning).isTrue()
+ }
+ }
+
+ @Test
+ fun flagShowUiWarnings_isShowingSafetyWarning_isTrue() =
+ with(kosmos) {
+ runTest {
+ val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+ fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI_WARNINGS)
+
+ assertThat(isShowingSafetyWarning).isTrue()
+ }
+ }
+
+ @Test
+ fun invisibleAndNoFlags_isShowingSafetyWarning_isFalse() =
+ with(kosmos) {
+ runTest {
+ val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+ volumeDialogVisibilityRepository.updateVisibility {
+ VolumeDialogVisibilityModel.Invisible
+ }
+
+ fakeVolumeDialogController.onShowSafetyWarning(0)
+
+ assertThat(isShowingSafetyWarning).isFalse()
+ }
+ }
+
+ @Test
+ fun visibleAndNoFlags_isShowingSafetyWarning_isTrue() =
+ with(kosmos) {
+ runTest {
+ val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+ volumeDialogVisibilityRepository.updateVisibility {
+ VolumeDialogVisibilityModel.Visible(
+ Events.SHOW_REASON_VOLUME_CHANGED,
+ false,
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ )
+ }
+
+ fakeVolumeDialogController.onShowSafetyWarning(0)
+
+ assertThat(isShowingSafetyWarning).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
index 1e6e52a23658..d8184dbadf9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -83,7 +83,7 @@ class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
assertThat(ringerViewModel).isInstanceOf(RingerViewModelState.Available::class.java)
assertThat((ringerViewModel as RingerViewModelState.Available).uiModel.drawerState)
- .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
+ .isEqualTo(RingerDrawerState.Closed(normalRingerMode, normalRingerMode))
}
@Test
@@ -91,8 +91,9 @@ class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
testScope.runTest {
val ringerViewModel by collectLastValue(underTest.ringerViewModel)
val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
+ val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
- setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
+ setUpRingerModeAndOpenDrawer(normalRingerMode)
// Select vibrate ringer mode.
underTest.onRingerButtonClicked(vibrateRingerMode)
controller.getState()
@@ -103,7 +104,8 @@ class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
var uiModel = (ringerViewModel as RingerViewModelState.Available).uiModel
assertThat(uiModel.availableButtons[uiModel.currentButtonIndex]?.ringerMode)
.isEqualTo(vibrateRingerMode)
- assertThat(uiModel.drawerState).isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
+ assertThat(uiModel.drawerState)
+ .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode, normalRingerMode))
val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
// Open drawer
@@ -120,7 +122,8 @@ class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
uiModel = (ringerViewModel as RingerViewModelState.Available).uiModel
assertThat(uiModel.availableButtons[uiModel.currentButtonIndex]?.ringerMode)
.isEqualTo(silentRingerMode)
- assertThat(uiModel.drawerState).isEqualTo(RingerDrawerState.Closed(silentRingerMode))
+ assertThat(uiModel.drawerState)
+ .isEqualTo(RingerDrawerState.Closed(silentRingerMode, vibrateRingerMode))
assertThat(controller.hasScheduledTouchFeedback).isFalse()
assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt
new file mode 100644
index 000000000000..773c2a2d5e78
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.plugins
+
+import android.os.IBinder
+import android.view.View
+import com.android.systemui.plugins.annotations.ProvidesInterface
+
+/**
+ * Plugin for experimental "Contextual Auth" features.
+ *
+ * These plugins will get raw access to low-level events about the user's environment, such as
+ * moving in/out of trusted locations, connection status of trusted devices, auth attempts, etc.
+ * They will also receive callbacks related to system events & transitions to enable prototypes on
+ * sensitive surfaces like lock screen and BiometricPrompt.
+ *
+ * Note to rebuild the plugin jar run: m PluginDummyLib
+ */
+@ProvidesInterface(action = AuthContextPlugin.ACTION, version = AuthContextPlugin.VERSION)
+interface AuthContextPlugin : Plugin {
+
+ /**
+ * Called in the background when the plugin is enabled.
+ *
+ * This is a good time to ask your friendly [saucier] to cook up something special. The
+ * [Plugin.onCreate] can also be used for initialization.
+ */
+ fun activated(saucier: Saucier)
+
+ /**
+ * Called when a [SensitiveSurface] is first shown.
+ *
+ * This may be called repeatedly if the state of the surface changes after it is shown. For
+ * example, [SensitiveSurface.BiometricPrompt.isCredential] will change if the user falls back
+ * to a credential-based auth method.
+ */
+ fun onShowingSensitiveSurface(surface: SensitiveSurface)
+
+ /**
+ * Called when a [SensitiveSurface] sensitive surface is hidden.
+ *
+ * This method may still be called without [onShowingSensitiveSurface] in cases of rapid
+ * dismissal and plugins implementations should typically be idempotent.
+ */
+ fun onHidingSensitiveSurface(surface: SensitiveSurface)
+
+ companion object {
+ /** Plugin action. */
+ const val ACTION = "com.android.systemui.action.PLUGIN_AUTH_CONTEXT"
+ /** Plugin version. */
+ const val VERSION = 1
+ }
+
+ /** Information about a sensitive surface in the framework, which the Plugin may augment. */
+ sealed interface SensitiveSurface {
+
+ /** Information about the BiometricPrompt that is being shown to the user. */
+ data class BiometricPrompt(val view: View? = null, val isCredential: Boolean = false) :
+ SensitiveSurface
+
+ /** Information about bouncer. */
+ data class LockscreenBouncer(val view: View? = null) : SensitiveSurface
+ }
+
+ /** Ask for the special. */
+ interface Saucier {
+
+ /** What [flavor] would you like? */
+ fun getSauce(flavor: String): IBinder?
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index e3cbd6643e5f..322da322ff0c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -14,7 +14,6 @@
package com.android.systemui.plugins.qs;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -22,6 +21,7 @@ import android.metrics.LogMaker;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
@@ -92,6 +92,7 @@ public interface QSTile {
CharSequence getTileLabel();
+ @NonNull
State getState();
default LogMaker populate(LogMaker logMaker) {
@@ -269,6 +270,7 @@ public interface QSTile {
return sb.append(']');
}
+ @NonNull
public State copy() {
State state = new State();
copyTo(state);
@@ -304,6 +306,7 @@ public interface QSTile {
return rt;
}
+ @androidx.annotation.NonNull
@Override
public State copy() {
AdapterState state = new AdapterState();
@@ -316,6 +319,7 @@ public interface QSTile {
class BooleanState extends AdapterState {
public static final int VERSION = 1;
+ @androidx.annotation.NonNull
@Override
public State copy() {
BooleanState state = new BooleanState();
diff --git a/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
new file mode 100644
index 000000000000..0640116a4e97
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,616 L240,376l56,-56 184,184 184,-184 56,56 -240,240Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
new file mode 100644
index 000000000000..65a3eef4bdf1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,432 L296,616l-56,-56 240,-240 240,240 -56,56 -184,-184Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index d8967d4706ce..e90471e75287 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -22,7 +22,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
- android:paddingTop="@dimen/controls_management_top_padding"
android:paddingStart="@dimen/controls_management_side_padding"
android:paddingEnd="@dimen/controls_management_side_padding" >
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index ff5d9d3567f6..a338e4c70cfa 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -29,16 +29,18 @@
android:layout_height="@dimen/notification_2025_single_line_face_pile_size"
android:layout_marginEnd="8dp"
>
- <ImageView
+ <com.android.internal.widget.CachingIconView
android:id="@*android:id/conversation_icon"
android:layout_width="@dimen/notification_2025_single_line_avatar_size"
android:layout_height="@dimen/notification_2025_single_line_avatar_size"
android:layout_gravity="center_vertical|end"
+ android:background="@*android:drawable/notification_icon_circle"
+ android:clipToOutline="true"
/>
<ViewStub
android:id="@*android:id/conversation_face_pile"
- android:layout="@*android:layout/conversation_face_pile_layout"
+ android:layout="@*android:layout/notification_2025_conversation_face_pile_layout"
android:layout_width="@dimen/notification_2025_single_line_face_pile_size"
android:layout_height="@dimen/notification_2025_single_line_face_pile_size"
android:layout_gravity="center_vertical|end"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 4b5cdb5e708d..c6a9470a94b2 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -29,7 +29,7 @@
android:tint="?android:attr/textColorSecondary"
android:layout_gravity="center_vertical"
android:layout_weight="0"
- android:layout_marginRight="@dimen/screenrecord_option_padding"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding"
android:importantForAccessibility="no"/>
<Spinner
android:id="@+id/screen_recording_options"
@@ -63,7 +63,7 @@
android:layout_height="@dimen/screenrecord_option_icon_size"
android:src="@drawable/ic_touch"
android:tint="?android:attr/textColorSecondary"
- android:layout_marginRight="@dimen/screenrecord_option_padding"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding"
android:importantForAccessibility="no"/>
<TextView
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index c1852b106544..9ac456c17084 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,15 +14,21 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
<com.google.android.material.slider.Slider
- style="@style/SystemUI.Material3.Slider.Volume"
android:id="@+id/volume_dialog_slider"
- android:layout_width="@dimen/volume_dialog_slider_height"
- android:layout_height="match_parent"
+ style="@style/SystemUI.Material3.Slider.Volume"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height"
android:layout_gravity="center"
- android:rotation="270"
- android:theme="@style/Theme.Material3.Light" />
-</FrameLayout>
+ android:theme="@style/Theme.Material3.Light"
+ android:orientation="vertical"
+ app:thumbHeight="52dp"
+ app:trackCornerSize="12dp"
+ app:trackHeight="40dp"
+ app:trackStopIndicatorSize="6dp"
+ app:trackInsideCornerSize="2dp" />
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json
index 10768fccd4ad..28662669ec25 100644
--- a/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json
+++ b/packages/SystemUI/res/raw/trackpad_recent_apps_edu.json
@@ -1 +1 @@
-{"v":"5.12.1","fr":60,"ip":0,"op":511,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":0,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[0],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.001],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.002],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.003],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.004],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.006],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.008],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.012],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.016],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.02],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.025],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.031],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.038],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.047],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.059],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.073],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.091],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.116],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.15],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.196],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.249],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.306],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.366],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.425],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.481],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.53],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.575],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.614],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.648],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.678],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.706],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.73],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.752],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.772],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.79],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.807],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.822],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.836],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.849],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.861],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.873],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.883],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.892],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.901],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.91],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.917],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.925],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.931],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.937],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.943],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.949],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.954],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.958],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.963],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.967],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.97],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.974],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.977],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.98],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.983],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.985],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.987],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.989],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.991],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.993],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.994],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.996],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.997],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.999],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.009],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.038],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.093],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.193],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.4],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.636],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.739],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.8],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.84],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.871],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.894],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.912],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.928],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.94],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.951],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.959],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.967],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.973],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.979],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.983],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.987],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.99],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.993],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.995],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.997],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.999],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}],"ix":1}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0,"ix":1}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[252,278,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,278.45,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,279.615,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,281.252,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,283.166,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,287.233,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,289.181,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,290.982,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,292.599,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,294.012,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,295.216,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,296.216,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.023,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.655,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.131,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.474,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.705,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.465,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.226,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":377,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.382,0],"t":378,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,299.372,0],"t":379,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,300.764,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,302.391,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,305.848,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,307.504,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,309.035,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,310.409,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,311.611,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,312.634,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,313.483,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.169,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.706,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.112,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.403,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.717,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.474,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.192,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"k":[{"s":[26.984],"t":127,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":128,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.95],"t":129,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.921],"t":130,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.882],"t":131,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.83],"t":132,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.765],"t":133,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.685],"t":134,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.589],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.478],"t":136,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.349],"t":137,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.205],"t":138,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.072],"t":139,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.926],"t":140,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.764],"t":141,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.589],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.397],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.187],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.711],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.44],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.146],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.826],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.479],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.1],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.686],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.236],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.745],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.207],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.616],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.967],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.248],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.457],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.578],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.602],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.514],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.303],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[12.954],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.477],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[9.885],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[8.215],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.526],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.878],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[3.338],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.659],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.475],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-1.485],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-2.388],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.192],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.911],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-4.556],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.136],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.662],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.135],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.563],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.951],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.303],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.622],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.913],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.175],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.413],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.628],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.823],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.998],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.155],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.296],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.42],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.531],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.627],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.711],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.783],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.843],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.893],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.933],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.963],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.984],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.996],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":252,"ix":3},"y":{"k":[{"s":[157.385],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.28],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.128],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.026],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.901],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.75],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.564],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.335],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.054],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.706],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.275],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.73],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.03],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.103],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.8],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[150.035],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[148.047],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.867],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.589],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.341],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.241],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[137.346],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[135.666],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[134.185],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[132.878],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[131.718],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.684],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[129.755],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.916],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.155],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[127.462],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.829],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.249],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.715],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.221],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.765],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.343],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.951],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.587],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.249],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.934],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.641],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.369],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.114],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.877],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.657],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.452],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.26],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.082],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.918],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.764],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.623],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.492],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.371],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.261],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.158],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.065],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.98],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.903],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.835],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.718],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.629],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.51],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.094],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.397],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.982],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[149.027],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.2],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.675],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.764],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.396],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.825],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.141],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.386],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.581],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.74],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.871],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.981],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.074],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.218],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.362],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[503.613,314.758],"t":144,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.134,314.459],"t":146,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.832,314.27],"t":147,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.464,314.04],"t":148,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.025,313.765],"t":149,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.487,313.429],"t":150,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.824,313.015],"t":151,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.023,312.514],"t":152,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.032,311.895],"t":153,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[497.818,311.136],"t":154,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.328,310.205],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[494.484,309.053],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[492.194,307.621],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[489.307,305.817],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[485.592,303.495],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.67,300.419],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[473.76,296.1],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[464.395,290.247],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[453.849,283.656],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[442.286,276.429],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[430.198,268.874],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[418.274,261.421],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[407.131,254.457],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[397.077,248.173],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[388.165,242.603],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[380.31,237.694],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[373.373,233.358],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[367.218,229.511],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[361.732,226.082],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[356.803,223.002],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[352.354,220.221],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[348.318,217.699],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[344.643,215.402],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[341.283,213.302],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[338.205,211.378],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[335.37,209.606],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[332.752,207.97],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[330.33,206.456],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[328.092,205.058],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[326.012,203.757],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[324.082,202.552],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[322.291,201.432],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[320.617,200.386],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[319.062,199.414],"t":188,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[317.618,198.512],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[316.267,197.667],"t":190,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[315.013,196.883],"t":191,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[313.845,196.153],"t":192,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[312.756,195.472],"t":193,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[311.738,194.837],"t":194,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[310.793,194.246],"t":195,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.921,193.7],"t":196,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.107,193.192],"t":197,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[308.359,192.724],"t":198,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.663,192.289],"t":199,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.02,191.888],"t":200,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[306.436,191.522],"t":201,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.891,191.182],"t":202,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.399,190.874],"t":203,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.946,190.591],"t":204,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.539,190.337],"t":205,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.178,190.112],"t":206,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.85,189.906],"t":207,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.555,189.722],"t":208,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.306,189.566],"t":209,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.082,189.427],"t":210,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.892,189.308],"t":211,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.617,189.135],"t":213,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.4,189],"t":250,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.264,178.29],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.222,179.514],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[293.538,183.461],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.714,191.071],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[327.48,204.675],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[372.758,232.974],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[424.317,265.198],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[447.009,279.381],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[460.167,287.605],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[469.103,293.19],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[475.697,297.31],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.788,300.492],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[484.853,303.033],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[488.172,305.107],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[490.906,306.816],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[493.198,308.249],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[495.121,309.451],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.752,310.47],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[498.133,311.333],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.301,312.063],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.29,312.681],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.123,313.202],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.814,313.634],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.391,313.994],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.861,314.288],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.238,314.524],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.53,314.706],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"k":[{"s":[27.974],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.942],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.922],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.898],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.869],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.833],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.789],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.736],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.67],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.589],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.49],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.368],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.216],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.024],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.777],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.45],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.991],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.37],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.669],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.901],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.098],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.306],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.566],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.898],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.306],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.785],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.324],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.915],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.551],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.223],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.928],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.66],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.416],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.193],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.988],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.8],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.626],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.465],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.316],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.178],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.05],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.931],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.82],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.717],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.621],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.531],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.448],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.37],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.298],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.23],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.167],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.11],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.055],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.006],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.96],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.917],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.878],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.842],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.809],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.779],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.752],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.728],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.706],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.687],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.67],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.655],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.643],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.633],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.624],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.61],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.907],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.318],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.109],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.524],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.468],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.82],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.295],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.15],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.731],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.16],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.491],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.755],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.149],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.298],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.423],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.529],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.619],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.694],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.759],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.813],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.858],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.895],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.926],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.95],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.969],"t":406,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.993],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"k":[{"s":[99.923,99.923,100],"t":144,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.828,99.828,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.768,99.768,100],"t":147,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.695,99.695,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.608,99.608,100],"t":149,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.501,99.501,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.37,99.37,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.211,99.211,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.014,99.014,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.773,98.773,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.478,98.478,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.112,98.112,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.658,97.658,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.085,97.085,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.348,96.348,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.371,95.371,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94,94,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.142,92.142,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.049,90.049,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.755,87.755,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.357,85.357,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.991,82.991,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.78,80.78,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[78.785,78.785,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[77.017,77.017,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[75.458,75.458,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[74.082,74.082,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[72.861,72.861,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[71.772,71.772,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70.794,70.794,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.911,69.911,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.111,69.111,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.382,68.382,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.715,67.715,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.104,67.104,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.542,66.542,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.022,66.022,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.542,65.542,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.098,65.098,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.685,64.685,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.302,64.302,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.947,63.947,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.615,63.615,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.306,63.306,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.02,63.02,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.751,62.751,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.503,62.503,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.271,62.271,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.055,62.055,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.853,61.853,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.665,61.665,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.492,61.492,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.331,61.331,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.182,61.182,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.044,61.044,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.917,60.917,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.801,60.801,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.693,60.693,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.595,60.595,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.505,60.505,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.424,60.424,100],"t":205,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.353,60.353,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.288,60.288,100],"t":207,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.229,60.229,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.18,60.18,100],"t":209,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.135,60.135,100],"t":210,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.098,60.098,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.043,60.043,100],"t":213,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60,60,100],"t":250,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.6,56.6,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.989,56.989,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.242,58.242,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.657,60.657,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.976,64.976,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[73.96,73.96,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.19,84.19,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.692,88.692,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.303,91.303,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.076,93.076,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.384,94.384,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.394,95.394,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.201,96.201,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.859,96.859,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.402,97.402,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.857,97.857,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.238,98.238,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.562,98.562,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.836,98.836,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.068,99.068,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.264,99.264,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.429,99.429,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.566,99.566,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.681,99.681,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.774,99.774,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.849,99.849,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.907,99.907,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,-30.035,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[176.678,176.678,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-53.175,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":510,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[115.3,72.35],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13.78,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"app - 5","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[51.5,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.652,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.136,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.013,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.449,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.806,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.3,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.437,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[68.94,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[70.432,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.462,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.229,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.83,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.314,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.714,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.048,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.334,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.578,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.789,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.971,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.131,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.269,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.389,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.493,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.584,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.663,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.731,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.789,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.839,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.915,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.982,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.779,0,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.066,0,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.709,0,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.271,0,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.2,0,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[60.425,0,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.886,0,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.41,0,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.409,0,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.67,0,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.1,0,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.646,0,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.275,0,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.968,0,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.711,0,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.495,0,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.312,0,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.157,0,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.026,0,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.916,0,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.822,0,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.745,0,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.68,0,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.628,0,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.585,0,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.552,0,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.501,0,0],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[167,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7511","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[167,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"app - 4","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[123.341,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.654,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.223,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.146,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.662,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.549,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.838,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[134.455,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.421,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.083,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.576,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.962,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.272,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.528,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.742,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.924,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.08,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.216,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.334,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.437,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.527,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.606,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.734,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.869,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.864,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.402,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.527,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.96,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.701,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.002,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[127.358,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.406,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.763,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.288,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.923,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.633,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.396,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.199,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.034,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.895,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.776,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.675,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.589,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.516,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.403,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.299,15,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[139,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7508","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[139,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"app - 3","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[104.041,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.182,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.436,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.844,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.517,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.808,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.265,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.981,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.703,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.923,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.092,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.228,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.341,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.436,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.517,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.587,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.648,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.746,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.853,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.956,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.938,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.73,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.34,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.649,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.197,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.559,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.828,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.403,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.117,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.906,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.745,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.616,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.511,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.424,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.35,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.288,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.19,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.091,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[111,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7507","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[111,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"app - 2","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[84.704,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.639,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.537,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.371,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.048,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.684,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.505,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.398,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.324,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.271,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.195,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.123,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.045,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.068,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.166,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.338,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.702,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.112,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.294,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.399,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.47,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.521,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.593,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.676,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[83,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7506","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[83,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"app - 1","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[65.439,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.229,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.849,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.236,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.226,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.296,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.111,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.033,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.388,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.945,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.616,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.359,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.154,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.984,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.842,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.72,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.616,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.525,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.447,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.378,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.317,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.265,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.219,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.178,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.143,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.113,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.066,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.012,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.092,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.403,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.986,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.027,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.212,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.67,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[62.762,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.396,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.825,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.141,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.385,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.578,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.736,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.867,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.977,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.07,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.149,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.217,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.274,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.323,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.364,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.426,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.491,15,0],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[55,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7505","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[55,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"divider","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90,"ix":10},"p":{"k":[{"s":[51,15,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.913,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.615,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.073,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.194,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.751,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.001,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.869,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.328,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.778,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.309,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.941,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.645,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.402,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.199,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.025,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.876,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.747,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.635,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.536,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.451,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.376,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.31,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.253,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.203,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.161,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.124,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.093,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.068,15,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.047,15,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.017,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.129,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.569,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.403,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.895,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.999,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.522,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.088,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.994,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[48.607,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.059,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.407,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.683,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.909,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.096,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.253,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.386,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.499,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.596,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.677,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.747,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.806,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.854,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.895,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.927,15,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.973,15,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-0.5],[2,-0.5]],"c":false}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.019,-0.5],[2.019,-0.5]],"c":false}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.077,-0.5],[2.077,-0.5]],"c":false}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.185,-0.5],[2.185,-0.5]],"c":false}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.361,-0.5],[2.361,-0.5]],"c":false}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.65,-0.5],[2.65,-0.5]],"c":false}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.2,-0.5],[3.2,-0.5]],"c":false}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.829,-0.5],[3.829,-0.5]],"c":false}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.136,-0.5],[4.136,-0.5]],"c":false}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.318,-0.5],[4.318,-0.5]],"c":false}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.444,-0.5],[4.444,-0.5]],"c":false}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.538,-0.5],[4.538,-0.5]],"c":false}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.612,-0.5],[4.612,-0.5]],"c":false}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.671,-0.5],[4.671,-0.5]],"c":false}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.761,-0.5],[4.761,-0.5]],"c":false}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.796,-0.5],[4.796,-0.5]],"c":false}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.826,-0.5],[4.826,-0.5]],"c":false}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.852,-0.5],[4.852,-0.5]],"c":false}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.874,-0.5],[4.874,-0.5]],"c":false}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.894,-0.5],[4.894,-0.5]],"c":false}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.91,-0.5],[4.91,-0.5]],"c":false}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.925,-0.5],[4.925,-0.5]],"c":false}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.938,-0.5],[4.938,-0.5]],"c":false}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.949,-0.5],[4.949,-0.5]],"c":false}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.959,-0.5],[4.959,-0.5]],"c":false}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.967,-0.5],[4.967,-0.5]],"c":false}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.974,-0.5],[4.974,-0.5]],"c":false}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.98,-0.5],[4.98,-0.5]],"c":false}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.99,-0.5],[4.99,-0.5]],"c":false}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.996,-0.5],[4.996,-0.5]],"c":false}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.973,-0.5],[4.973,-0.5]],"c":false}],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.887,-0.5],[4.887,-0.5]],"c":false}],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.421,-0.5],[4.421,-0.5]],"c":false}],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.8,-0.5],[3.8,-0.5]],"c":false}],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.093,-0.5],[3.093,-0.5]],"c":false}],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.782,-0.5],[2.782,-0.5]],"c":false}],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.601,-0.5],[2.601,-0.5]],"c":false}],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.479,-0.5],[2.479,-0.5]],"c":false}],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.388,-0.5],[2.388,-0.5]],"c":false}],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.318,-0.5],[2.318,-0.5]],"c":false}],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.263,-0.5],[2.263,-0.5]],"c":false}],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.217,-0.5],[2.217,-0.5]],"c":false}],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.18,-0.5],[2.18,-0.5]],"c":false}],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.148,-0.5],[2.148,-0.5]],"c":false}],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.122,-0.5],[2.122,-0.5]],"c":false}],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.099,-0.5],[2.099,-0.5]],"c":false}],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.081,-0.5],[2.081,-0.5]],"c":false}],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.064,-0.5],[2.064,-0.5]],"c":false}],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.051,-0.5],[2.051,-0.5]],"c":false}],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.039,-0.5],[2.039,-0.5]],"c":false}],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.03,-0.5],[2.03,-0.5]],"c":false}],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.022,-0.5],[2.022,-0.5]],"c":false}],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.01,-0.5],[2.01,-0.5]],"c":false}],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.006,-0.5],[2.006,-0.5]],"c":false}],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.004,-0.5],[2.004,-0.5]],"c":false}],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.001,-0.5],[2.001,-0.5]],"c":false}],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"divider","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-52.349,0.652,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.453,0.652,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.813,0.652,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.464,0.652,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.515,0.652,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.247,0.652,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.565,0.652,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.323,0.652,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-65.162,0.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.258,0.652,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.015,0.652,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.578,0.652,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.019,0.652,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.375,0.652,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.668,0.652,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.914,0.652,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.122,0.652,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.301,0.652,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.456,0.652,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.59,0.652,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.708,0.652,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.81,0.652,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.9,0.652,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.978,0.652,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.047,0.652,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.106,0.652,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.157,0.652,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.2,0.652,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.268,0.652,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.328,0.652,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.193,0.652,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.662,0.652,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.662,0.652,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.874,0.652,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.132,0.652,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.906,0.652,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.04,0.652,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.956,0.652,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.22,0.652,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.678,0.652,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.259,0.652,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.925,0.652,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.654,0.652,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.43,0.652,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.241,0.652,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.082,0.652,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.947,0.652,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.831,0.652,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.734,0.652,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.65,0.652,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.58,0.652,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.522,0.652,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.474,0.652,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.435,0.652,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.404,0.652,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.354,0.652,0],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[6.826,6.826,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0.4,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[91,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"k":[{"s":[120,4],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.383,4.161],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.592,4.668],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.818,5.601],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[127.463,7.13],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[133.427,9.631],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[144.8,14.4],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.801,19.852],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[164.141,22.511],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[167.915,24.093],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.516,25.184],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[172.458,25.999],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[173.978,26.636],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[175.203,27.15],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.216,27.574],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.065,27.931],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.788,28.234],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.406,28.493],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.938,28.716],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.399,28.909],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.8,29.078],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.149,29.224],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.454,29.352],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.718,29.463],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.949,29.559],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.148,29.643],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.32,29.715],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.467,29.777],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.592,29.829],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.697,29.873],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.784,29.91],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.855,29.939],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.909,29.962],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.978,29.991],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.445,29.767],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.655,29.017],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.204,27.569],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.034,24.982],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.2,19.6],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[142.586,13.472],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[136.154,10.774],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[132.424,9.21],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[129.891,8.148],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[128.022,7.364],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[126.579,6.759],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[125.427,6.276],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[124.487,5.881],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.712,5.556],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.062,5.284],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.517,5.055],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.055,4.862],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.663,4.697],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.332,4.559],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.051,4.441],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.815,4.342],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.62,4.26],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.456,4.191],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.323,4.135],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.216,4.091],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.133,4.056],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.073,4.03],"t":407,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.03,4.013],"t":408,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.008,4.003],"t":409,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32.672,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Taskbar Lofi","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[339.937,151.75,0],"ix":2,"l":2},"a":{"a":0,"k":[339.937,151.75,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[334,279],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[334,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82,171.125,0],"ix":2,"l":2},"a":{"a":0,"k":[82,171.125,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82.5,140.5,0],"ix":2,"l":2},"a":{"a":0,"k":[82,140.938,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Search","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"header","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 6","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 3","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,171],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"block","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 1","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app only","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[100]},{"t":256,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"right circle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[-41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"left circle","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"size","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Recents_EDU Loop","parent":5,"tt":1,"tp":5,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":511,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}],"markers":[{"tm":142,"cm":"drag with gesture","dr":108},{"tm":217,"cm":"onPause","dr":0},{"tm":250,"cm":"release playback realtime","dr":36}],"props":{}} \ No newline at end of file
+{"v":"5.12.1","fr":60,"ip":0,"op":511,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":0,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[0],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.001],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.002],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.003],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.004],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.006],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.008],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.012],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.016],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.02],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.025],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.031],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.038],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.047],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.059],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.073],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.091],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.116],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.15],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.196],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.249],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.306],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.366],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.425],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.481],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.53],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.575],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.614],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.648],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.678],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.706],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.73],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.752],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.772],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.79],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.807],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.822],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.836],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.849],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.861],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.873],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.883],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.892],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.901],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.91],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.917],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.925],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.931],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.937],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.943],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.949],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.954],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.958],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.963],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.967],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.97],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.974],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.977],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.98],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.983],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.985],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.987],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.989],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.991],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.993],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.994],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.996],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.997],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.998],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.999],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.009],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.038],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.093],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.193],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.4],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.636],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.739],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.8],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.84],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.871],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.894],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.912],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.928],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.94],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.951],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.959],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.967],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.973],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.979],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.983],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.987],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.99],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.993],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.995],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.997],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[2.999],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}],"ix":1}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0,"ix":1}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[252,278,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,278.45,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,279.615,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,281.252,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,283.166,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,287.233,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,289.181,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,290.982,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,292.599,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,294.012,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,295.216,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,296.216,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.023,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,297.655,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.131,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.474,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.705,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.465,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.226,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":377,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298.382,0],"t":378,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,299.372,0],"t":379,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,300.764,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,302.391,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,305.848,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,307.504,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,309.035,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,310.409,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,311.611,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,312.634,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,313.483,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.169,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,314.706,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.112,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.403,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.717,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.474,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,315.192,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"k":[{"s":[26.984],"t":127,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":128,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.95],"t":129,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.921],"t":130,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.882],"t":131,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.83],"t":132,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.765],"t":133,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.685],"t":134,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.589],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.478],"t":136,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.349],"t":137,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.205],"t":138,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.072],"t":139,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.926],"t":140,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.764],"t":141,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.589],"t":142,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.397],"t":143,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.187],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.711],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.44],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.146],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.826],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.479],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.1],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.686],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.236],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.745],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.207],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.616],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.967],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.248],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.457],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.578],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.602],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.514],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.303],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[12.954],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.477],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[9.885],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[8.215],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.526],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.878],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[3.338],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.659],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.475],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-1.485],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-2.388],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.192],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-3.911],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-4.556],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.136],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-5.662],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.135],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.563],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.951],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.303],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.622],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-7.913],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.175],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.413],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.628],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.823],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.998],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.155],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.296],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.42],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.531],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.627],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.711],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.783],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.843],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.893],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.933],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.963],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.984],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-9.996],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":252,"ix":3},"y":{"k":[{"s":[157.385],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.28],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.128],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.026],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.901],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.75],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.564],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.335],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.054],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.706],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.275],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.73],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.03],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.103],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.8],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[150.035],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[148.047],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.867],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.589],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.341],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.241],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[137.346],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[135.666],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[134.185],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[132.878],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[131.718],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.684],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[129.755],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.916],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.155],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[127.462],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.829],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[126.249],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.715],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[125.221],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.765],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.343],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.951],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.587],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.249],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.934],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.641],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.369],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.114],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.877],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.657],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.452],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.26],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[121.082],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.918],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.764],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.623],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.492],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.371],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.261],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.158],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.065],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.98],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.903],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.835],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.718],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.629],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.51],"t":215,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.094],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.397],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[147.982],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[149.027],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[151.2],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[153.675],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[154.764],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.396],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[155.825],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.141],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.386],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.581],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.74],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.871],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[156.981],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.074],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.218],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[157.362],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[503.613,314.758],"t":144,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.134,314.459],"t":146,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.832,314.27],"t":147,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.464,314.04],"t":148,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.025,313.765],"t":149,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.487,313.429],"t":150,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.824,313.015],"t":151,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.023,312.514],"t":152,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.032,311.895],"t":153,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[497.818,311.136],"t":154,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.328,310.205],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[494.484,309.053],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[492.194,307.621],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[489.307,305.817],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[485.592,303.495],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.67,300.419],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[473.76,296.1],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[464.395,290.247],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[453.849,283.656],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[442.286,276.429],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[430.198,268.874],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[418.274,261.421],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[407.131,254.457],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[397.077,248.173],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[388.165,242.603],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[380.31,237.694],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[373.373,233.358],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[367.218,229.511],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[361.732,226.082],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[356.803,223.002],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[352.354,220.221],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[348.318,217.699],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[344.643,215.402],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[341.283,213.302],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[338.205,211.378],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[335.37,209.606],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[332.752,207.97],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[330.33,206.456],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[328.092,205.058],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[326.012,203.757],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[324.082,202.552],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[322.291,201.432],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[320.617,200.386],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[319.062,199.414],"t":188,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[317.618,198.512],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[316.267,197.667],"t":190,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[315.013,196.883],"t":191,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[313.845,196.153],"t":192,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[312.756,195.472],"t":193,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[311.738,194.837],"t":194,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[310.793,194.246],"t":195,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.921,193.7],"t":196,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[309.107,193.192],"t":197,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[308.359,192.724],"t":198,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.663,192.289],"t":199,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[307.02,191.888],"t":200,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[306.436,191.522],"t":201,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.891,191.182],"t":202,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.399,190.874],"t":203,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.946,190.591],"t":204,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.539,190.337],"t":205,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[304.178,190.112],"t":206,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.85,189.906],"t":207,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.555,189.722],"t":208,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.306,189.566],"t":209,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[303.082,189.427],"t":210,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.892,189.308],"t":211,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.617,189.135],"t":213,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.4,189],"t":250,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.264,178.29],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.222,179.514],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[293.538,183.461],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[305.714,191.071],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[327.48,204.675],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[372.758,232.974],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[424.317,265.198],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[447.009,279.381],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[460.167,287.605],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[469.103,293.19],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[475.697,297.31],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[480.788,300.492],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[484.853,303.033],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[488.172,305.107],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[490.906,306.816],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[493.198,308.249],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[495.121,309.451],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[496.752,310.47],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[498.133,311.333],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[499.301,312.063],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[500.29,312.681],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.123,313.202],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[501.814,313.634],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.391,313.994],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[502.861,314.288],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.238,314.524],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[503.53,314.706],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"k":[{"s":[27.974],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.959],"t":145,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.942],"t":146,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.922],"t":147,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.898],"t":148,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.869],"t":149,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.833],"t":150,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.789],"t":151,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.736],"t":152,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.67],"t":153,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.589],"t":154,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.49],"t":155,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.368],"t":156,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.216],"t":157,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.024],"t":158,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.777],"t":159,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.45],"t":160,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.991],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.37],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.669],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.901],"t":164,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.098],"t":165,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.306],"t":166,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.566],"t":167,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.898],"t":168,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.306],"t":169,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.785],"t":170,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.324],"t":171,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.915],"t":172,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.551],"t":173,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[18.223],"t":174,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.928],"t":175,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.66],"t":176,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.416],"t":177,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.193],"t":178,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.988],"t":179,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.8],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.626],"t":181,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.465],"t":182,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.316],"t":183,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.178],"t":184,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.05],"t":185,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.931],"t":186,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.82],"t":187,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.717],"t":188,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.621],"t":189,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.531],"t":190,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.448],"t":191,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.37],"t":192,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.298],"t":193,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.23],"t":194,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.167],"t":195,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.11],"t":196,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.055],"t":197,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.006],"t":198,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.96],"t":199,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.917],"t":200,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.878],"t":201,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.842],"t":202,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.809],"t":203,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.779],"t":204,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.752],"t":205,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.728],"t":206,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.706],"t":207,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.687],"t":208,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.67],"t":209,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.655],"t":210,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.643],"t":211,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.633],"t":212,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.624],"t":213,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.61],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":380,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.907],"t":381,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.318],"t":382,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.109],"t":383,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.524],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.468],"t":385,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.82],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.295],"t":387,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.15],"t":388,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.731],"t":389,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.16],"t":390,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.491],"t":391,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.755],"t":392,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[26.971],"t":393,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.149],"t":394,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.298],"t":395,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.423],"t":396,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.529],"t":397,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.619],"t":398,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.694],"t":399,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.759],"t":400,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.813],"t":401,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.858],"t":402,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.895],"t":403,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.926],"t":404,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.95],"t":405,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.969],"t":406,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.993],"t":408,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"k":[{"s":[99.923,99.923,100],"t":144,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.828,99.828,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.768,99.768,100],"t":147,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.695,99.695,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.608,99.608,100],"t":149,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.501,99.501,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.37,99.37,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.211,99.211,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.014,99.014,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.773,98.773,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.478,98.478,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.112,98.112,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.658,97.658,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.085,97.085,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.348,96.348,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.371,95.371,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94,94,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.142,92.142,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.049,90.049,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.755,87.755,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.357,85.357,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.991,82.991,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.78,80.78,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[78.785,78.785,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[77.017,77.017,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[75.458,75.458,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[74.082,74.082,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[72.861,72.861,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[71.772,71.772,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70.794,70.794,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.911,69.911,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[69.111,69.111,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.382,68.382,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.715,67.715,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[67.104,67.104,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.542,66.542,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[66.022,66.022,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.542,65.542,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[65.098,65.098,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.685,64.685,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.302,64.302,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.947,63.947,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.615,63.615,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.306,63.306,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.02,63.02,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.751,62.751,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.503,62.503,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.271,62.271,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[62.055,62.055,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.853,61.853,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.665,61.665,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.492,61.492,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.331,61.331,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.182,61.182,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[61.044,61.044,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.917,60.917,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.801,60.801,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.693,60.693,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.595,60.595,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.505,60.505,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.424,60.424,100],"t":205,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.353,60.353,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.288,60.288,100],"t":207,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.229,60.229,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.18,60.18,100],"t":209,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.135,60.135,100],"t":210,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.098,60.098,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.043,60.043,100],"t":213,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60,60,100],"t":250,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.6,56.6,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.989,56.989,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.242,58.242,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.657,60.657,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[64.976,64.976,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[73.96,73.96,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.19,84.19,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.692,88.692,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.303,91.303,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.076,93.076,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.384,94.384,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.394,95.394,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.201,96.201,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.859,96.859,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.402,97.402,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.857,97.857,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.238,98.238,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.562,98.562,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.836,98.836,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.068,99.068,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.264,99.264,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.429,99.429,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.566,99.566,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.681,99.681,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.774,99.774,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.849,99.849,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.907,99.907,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,-30.035,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[176.678,176.678,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-53.175,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":510,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[115.3,72.35],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13.78,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[51.5,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.652,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.136,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.013,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.449,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.806,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.3,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.437,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[68.94,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[70.432,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.462,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.229,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[72.83,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.314,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.714,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.048,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.334,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.578,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.789,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[74.971,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.131,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.269,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.389,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.493,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.584,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.663,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.731,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.789,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.839,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.915,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.982,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.779,0,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[75.066,0,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[73.709,0,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[71.271,0,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[66.2,0,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[60.425,0,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.886,0,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.41,0,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.409,0,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.67,0,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.1,0,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.646,0,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.275,0,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.968,0,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.711,0,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.495,0,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.312,0,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.157,0,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.026,0,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.916,0,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.822,0,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.745,0,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.68,0,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.628,0,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.585,0,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.552,0,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.501,0,0],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[167,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7511","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[167,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[123.341,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.654,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.223,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.146,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.662,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.549,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.838,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[134.455,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.421,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.083,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.576,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[136.962,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.272,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.528,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.742,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.924,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.08,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.216,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.334,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.437,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.527,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.606,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.734,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.869,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.864,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[138.402,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[137.527,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[135.96,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[132.701,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[129.002,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[127.358,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[126.406,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.763,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[125.288,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.923,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.633,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.396,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.199,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[124.034,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.895,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.776,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.675,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.589,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.516,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.403,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.299,15,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[139,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7508","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[139,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[104.041,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.182,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.436,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.844,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.517,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.808,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.265,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.981,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.703,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.923,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.092,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.228,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.341,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.436,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.517,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.587,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.648,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.746,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.853,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.956,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.938,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.73,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.34,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.649,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.197,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.559,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.828,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.403,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.117,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.906,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.745,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.616,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.511,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.424,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.35,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.288,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.19,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.091,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[111,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7507","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[111,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[84.704,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.639,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.537,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.371,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.048,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.684,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.505,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.398,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.324,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.271,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.195,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.123,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.045,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.068,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.166,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.338,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.702,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.112,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.294,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.399,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.47,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.521,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.593,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[84.676,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[83,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7506","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[83,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[65.439,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.229,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.849,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.236,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.226,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.296,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.111,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.033,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.388,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.945,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.616,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.359,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.154,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.984,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.842,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.72,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.616,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.525,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.447,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.378,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.317,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.265,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.219,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.178,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.143,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.113,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.066,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.012,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.092,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.403,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.986,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.027,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.212,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.67,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[62.762,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.396,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.825,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.141,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.385,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.578,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.736,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.867,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[64.977,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.07,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.149,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.217,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.274,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.323,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.364,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.426,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.491,15,0],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[55,15,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7505","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[55,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90,"ix":10},"p":{"k":[{"s":[51,15,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.913,15,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.615,15,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.073,15,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.194,15,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.751,15,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.001,15,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.869,15,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.328,15,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.409,15,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.778,15,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.309,15,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.941,15,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.645,15,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.402,15,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.199,15,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.025,15,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.876,15,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.747,15,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.635,15,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.536,15,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.451,15,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.376,15,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.31,15,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.253,15,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.203,15,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.161,15,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.124,15,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.093,15,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.068,15,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.047,15,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.017,15,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.129,15,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.569,15,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.403,15,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.895,15,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.999,15,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.522,15,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.088,15,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[47.994,15,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[48.607,15,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.059,15,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.407,15,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.683,15,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.909,15,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.096,15,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.253,15,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.386,15,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.499,15,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.596,15,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.677,15,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.747,15,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.806,15,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.854,15,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.895,15,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.927,15,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[50.973,15,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-0.5],[2,-0.5]],"c":false}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.019,-0.5],[2.019,-0.5]],"c":false}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.077,-0.5],[2.077,-0.5]],"c":false}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.185,-0.5],[2.185,-0.5]],"c":false}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.361,-0.5],[2.361,-0.5]],"c":false}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.65,-0.5],[2.65,-0.5]],"c":false}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.2,-0.5],[3.2,-0.5]],"c":false}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.829,-0.5],[3.829,-0.5]],"c":false}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.136,-0.5],[4.136,-0.5]],"c":false}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.318,-0.5],[4.318,-0.5]],"c":false}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.444,-0.5],[4.444,-0.5]],"c":false}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.538,-0.5],[4.538,-0.5]],"c":false}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.612,-0.5],[4.612,-0.5]],"c":false}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.671,-0.5],[4.671,-0.5]],"c":false}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.761,-0.5],[4.761,-0.5]],"c":false}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.796,-0.5],[4.796,-0.5]],"c":false}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.826,-0.5],[4.826,-0.5]],"c":false}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.852,-0.5],[4.852,-0.5]],"c":false}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.874,-0.5],[4.874,-0.5]],"c":false}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.894,-0.5],[4.894,-0.5]],"c":false}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.91,-0.5],[4.91,-0.5]],"c":false}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.925,-0.5],[4.925,-0.5]],"c":false}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.938,-0.5],[4.938,-0.5]],"c":false}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.949,-0.5],[4.949,-0.5]],"c":false}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.959,-0.5],[4.959,-0.5]],"c":false}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.967,-0.5],[4.967,-0.5]],"c":false}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.974,-0.5],[4.974,-0.5]],"c":false}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.98,-0.5],[4.98,-0.5]],"c":false}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.99,-0.5],[4.99,-0.5]],"c":false}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.996,-0.5],[4.996,-0.5]],"c":false}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.973,-0.5],[4.973,-0.5]],"c":false}],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.887,-0.5],[4.887,-0.5]],"c":false}],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.72,-0.5],[4.72,-0.5]],"c":false}],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.421,-0.5],[4.421,-0.5]],"c":false}],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.8,-0.5],[3.8,-0.5]],"c":false}],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.093,-0.5],[3.093,-0.5]],"c":false}],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.782,-0.5],[2.782,-0.5]],"c":false}],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.601,-0.5],[2.601,-0.5]],"c":false}],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.479,-0.5],[2.479,-0.5]],"c":false}],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.388,-0.5],[2.388,-0.5]],"c":false}],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.318,-0.5],[2.318,-0.5]],"c":false}],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.263,-0.5],[2.263,-0.5]],"c":false}],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.217,-0.5],[2.217,-0.5]],"c":false}],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.18,-0.5],[2.18,-0.5]],"c":false}],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.148,-0.5],[2.148,-0.5]],"c":false}],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.122,-0.5],[2.122,-0.5]],"c":false}],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.099,-0.5],[2.099,-0.5]],"c":false}],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.081,-0.5],[2.081,-0.5]],"c":false}],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.064,-0.5],[2.064,-0.5]],"c":false}],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.051,-0.5],[2.051,-0.5]],"c":false}],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.039,-0.5],[2.039,-0.5]],"c":false}],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.03,-0.5],[2.03,-0.5]],"c":false}],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.022,-0.5],[2.022,-0.5]],"c":false}],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.01,-0.5],[2.01,-0.5]],"c":false}],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.006,-0.5],[2.006,-0.5]],"c":false}],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.004,-0.5],[2.004,-0.5]],"c":false}],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.001,-0.5],[2.001,-0.5]],"c":false}],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"divider","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[0],"t":161,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[54.85],"t":162,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":163,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":384,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":386,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-52.349,0.652,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.453,0.652,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.813,0.652,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.464,0.652,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.515,0.652,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.247,0.652,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.565,0.652,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.323,0.652,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-65.162,0.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.258,0.652,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.015,0.652,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-67.578,0.652,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.019,0.652,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.375,0.652,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.668,0.652,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.914,0.652,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.122,0.652,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.301,0.652,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.456,0.652,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.59,0.652,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.708,0.652,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.81,0.652,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.9,0.652,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.978,0.652,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.047,0.652,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.106,0.652,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.157,0.652,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.2,0.652,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.268,0.652,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.328,0.652,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.193,0.652,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.662,0.652,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-68.662,0.652,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-66.874,0.652,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.132,0.652,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.906,0.652,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.04,0.652,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.956,0.652,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.22,0.652,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.678,0.652,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.259,0.652,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.925,0.652,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.654,0.652,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.43,0.652,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.241,0.652,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.082,0.652,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.947,0.652,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.831,0.652,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.734,0.652,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.65,0.652,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.58,0.652,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.522,0.652,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.474,0.652,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.435,0.652,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.404,0.652,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.354,0.652,0],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[6.826,6.826,0],"ix":1,"l":2},"s":{"k":[{"s":[50,50,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.309,50.309,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.284,51.284,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.079,53.079,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.019,56.019,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.828,60.828,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[70,70,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.485,80.485,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.597,85.597,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.641,88.641,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.739,90.739,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.305,92.305,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.53,93.53,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.518,94.518,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.335,95.335,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.021,96.021,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.603,96.603,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.101,97.101,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.531,97.531,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.902,97.902,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.226,98.226,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.507,98.507,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.753,98.753,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.966,98.966,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.152,99.152,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.313,99.313,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.451,99.451,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.671,99.671,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.756,99.756,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.826,99.826,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.883,99.883,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.982,99.982,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.552,99.552,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.109,98.109,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.326,95.326,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.35,90.35,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[68.215,68.215,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[63.027,63.027,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[60.02,60.02,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.977,57.977,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.47,56.47,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[55.306,55.306,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[54.377,54.377,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[53.618,53.618,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.993,52.993,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.469,52.469,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[52.03,52.03,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.657,51.657,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.341,51.341,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[51.074,51.074,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.848,50.848,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.658,50.658,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.5,50.5,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.368,50.368,100],"t":403,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.26,50.26,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.174,50.174,100],"t":405,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.107,50.107,100],"t":406,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.059,50.059,100],"t":407,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[50.024,50.024,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0.4,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[91,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"k":[{"s":[120,4],"t":155,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.383,4.161],"t":156,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.592,4.668],"t":157,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.818,5.601],"t":158,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[127.463,7.13],"t":159,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[133.427,9.631],"t":160,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[144.8,14.4],"t":161,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.801,19.852],"t":162,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[164.141,22.511],"t":163,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[167.915,24.093],"t":164,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.516,25.184],"t":165,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[172.458,25.999],"t":166,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[173.978,26.636],"t":167,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[175.203,27.15],"t":168,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.216,27.574],"t":169,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.065,27.931],"t":170,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[177.788,28.234],"t":171,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.406,28.493],"t":172,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[178.938,28.716],"t":173,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.399,28.909],"t":174,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.8,29.078],"t":175,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.149,29.224],"t":176,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.454,29.352],"t":177,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.718,29.463],"t":178,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[180.949,29.559],"t":179,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.148,29.643],"t":180,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.32,29.715],"t":181,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.467,29.777],"t":182,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.592,29.829],"t":183,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.697,29.873],"t":184,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.784,29.91],"t":185,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.855,29.939],"t":186,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.909,29.962],"t":187,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.978,29.991],"t":189,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":380,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[181.445,29.767],"t":381,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[179.655,29.017],"t":382,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[176.204,27.569],"t":383,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[170.034,24.982],"t":384,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[157.2,19.6],"t":385,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[142.586,13.472],"t":386,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[136.154,10.774],"t":387,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[132.424,9.21],"t":388,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[129.891,8.148],"t":389,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[128.022,7.364],"t":390,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[126.579,6.759],"t":391,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[125.427,6.276],"t":392,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[124.487,5.881],"t":393,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.712,5.556],"t":394,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[123.062,5.284],"t":395,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.517,5.055],"t":396,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[122.055,4.862],"t":397,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.663,4.697],"t":398,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.332,4.559],"t":399,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[121.051,4.441],"t":400,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.815,4.342],"t":401,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.62,4.26],"t":402,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.456,4.191],"t":403,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.323,4.135],"t":404,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.216,4.091],"t":405,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.133,4.056],"t":406,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.073,4.03],"t":407,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.03,4.013],"t":408,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[120.008,4.003],"t":409,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32.672,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Taskbar Lofi","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[339.937,151.75,0],"ix":2,"l":2},"a":{"a":0,"k":[339.937,151.75,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[334,279],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[334,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82,171.125,0],"ix":2,"l":2},"a":{"a":0,"k":[82,171.125,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82.5,140.5,0],"ix":2,"l":2},"a":{"a":0,"k":[82,140.938,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Search","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"header","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 6","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 3","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,171],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"block","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 1","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app only","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[100]},{"t":256,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"right circle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[-41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"left circle","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"size","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Recents_EDU Loop","parent":5,"tt":1,"tp":5,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":511,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}],"markers":[{"tm":142,"cm":"drag with gesture","dr":108},{"tm":217,"cm":"onPause","dr":0},{"tm":250,"cm":"release playback realtime","dr":36}],"props":{}} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_recent_apps_success.json b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json
index 1703c41df33a..21a9e135ce8c 100644
--- a/packages/SystemUI/res/raw/trackpad_recent_apps_success.json
+++ b/packages/SystemUI/res/raw/trackpad_recent_apps_success.json
@@ -1 +1 @@
-{"v":"5.12.1","fr":60,"ip":0,"op":97,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}],"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[95.049,95.049,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}],"ix":10},"p":{"a":0,"k":[81,127,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-0.289,"ix":10},"p":{"a":0,"k":[14.364,-33.591,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.125,0,0],"ix":1,"l":2},"s":{"a":0,"k":[104.744,104.744,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":11,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[95,95,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":88,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Checkbox - Widget","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Trackpad-JSON_Recents-EDU","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[100]},{"t":256,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-20,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-20,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"right circle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[-41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"left circle","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"size","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Recents_EDU Loop","parent":5,"tt":1,"tp":5,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":0,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}],"ix":1}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0,"ix":1}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[252,298.112,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"k":[{"s":[-10],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-10],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":252,"ix":3},"y":{"k":[{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"k":[{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"k":[{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,-30.035,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[176.678,176.678,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-53.175,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[115.3,72.35],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13.78,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"app - 5","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[76,0,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[167,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7511","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[167,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"app - 4","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[139,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[139,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[139,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7508","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[139,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"app - 3","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[111,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[111,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7507","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[111,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"app - 2","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[83,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[83,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7506","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[83,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"app - 1","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[55,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[55,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7505","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[55,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"divider","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90,"ix":10},"p":{"k":[{"s":[36,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"divider","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-70.349,0.652,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[6.826,6.826,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0.4,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[91,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"k":[{"s":[182,30],"t":217,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":292,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32.672,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Taskbar Lofi","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_4","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[339.937,151.75,0],"ix":2,"l":2},"a":{"a":0,"k":[339.937,151.75,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[334,279],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[334,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82,171.125,0],"ix":2,"l":2},"a":{"a":0,"k":[82,171.125,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82.5,140.5,0],"ix":2,"l":2},"a":{"a":0,"k":[82,140.938,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Search","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"header","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 6","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 3","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,171],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"block","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 1","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app only","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadAK_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,198.5,0],"ix":2,"l":2},"a":{"a":0,"k":[95,95,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":190,"h":190,"ip":53,"op":97,"st":53,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"track matte 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","tt":1,"tp":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":54,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":436,"s":[100]},{"t":439,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":4,"ix":1}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":47,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":96,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":47,"op":97,"st":47,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"track matte 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Trackpad-JSON_Recents-EDU","tt":1,"tp":5,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,282,0],"ix":2,"l":2},"a":{"a":0,"k":[277,282,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":554,"h":564,"ip":12,"op":58,"st":-235,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"track matte 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"Trackpad-JSON_Recents-EDU","tt":1,"tp":7,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,282,0],"ix":2,"l":2},"a":{"a":0,"k":[277,282,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":554,"h":564,"ip":-217,"op":33,"st":-217,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":4,"ix":1}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":96,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":221,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}} \ No newline at end of file
+{"v":"5.12.1","fr":60,"ip":0,"op":97,"w":554,"h":564,"nm":"Trackpad-JSON_Recents-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}],"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[95.049,95.049,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}],"ix":10},"p":{"a":0,"k":[81,127,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-0.289,"ix":10},"p":{"a":0,"k":[14.364,-33.591,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.125,0,0],"ix":1,"l":2},"s":{"a":0,"k":[104.744,104.744,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":11,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[95,95,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":88,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Checkbox - Widget","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Trackpad-JSON_Recents-EDU","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[100]},{"t":256,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-20,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-20,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"right circle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":248,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":258,"s":[-41,0]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"left circle","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":248,"s":[28,28]},{"t":258,"s":[36,36]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"size","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Recents_EDU Loop","parent":5,"tt":1,"tp":5,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Recents_EDU Loop","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"CNTL || playback","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":0,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Picker","np":3,"mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@WcSiov6sT3a4/s0XPKYEOQ-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"OUTPUT","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"k":[{"s":[1],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.009],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.038],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.093],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.193],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.4],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.636],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.739],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.8],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.84],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.871],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.894],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.912],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.928],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.94],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.951],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.959],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.967],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.973],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.979],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.983],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.987],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.99],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.993],"t":273,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.995],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.997],"t":275,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.998],"t":276,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.999],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}}]},{"ty":5,"nm":"Keys","np":3,"mn":"ADBE Slider Control","ix":3,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.831],"y":[0.109]},"o":{"x":[0.458],"y":[0.053]},"t":142,"s":[0]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.15],"y":[0.43]},"t":161,"s":[0.15]},{"t":217,"s":[1],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[1]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[1.4]},{"t":280,"s":[2],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[2]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":385,"s":[2.4]},{"t":410,"s":[3]}],"ix":1}}]},{"ty":5,"nm":"State (holds)","np":3,"mn":"ADBE Slider Control","ix":4,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0,"ix":1}}]}],"shapes":[],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null :: Taskbar drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[252,298.112,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252,298,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Taskbar Lofi","parent":3,"refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":134,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":143,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":432,"s":[100]},{"t":444,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"k":[{"s":[-10],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-10],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[91,15,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}],"ix":1}}]}],"w":182,"h":30,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Focus Task :: Lift & Drop","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":252,"ix":3},"y":{"k":[{"s":[119.5],"t":250,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[119.746],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[120.54],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[122.071],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[124.808],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[130.5],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[136.982],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[139.835],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[141.489],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[142.613],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[143.442],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.082],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[144.593],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.01],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.354],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.642],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[145.884],"t":266,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.089],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.262],"t":268,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.409],"t":269,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.534],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.638],"t":271,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.725],"t":272,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[146.857],"t":274,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"k":[{"s":[302.247,188.904],"t":251,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[301.752,188.595],"t":252,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[300.798,187.999],"t":253,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[299.093,186.933],"t":254,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[295.546,184.716],"t":255,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[291.506,182.192],"t":256,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[289.729,181.08],"t":257,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[288.698,180.436],"t":258,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.998,179.999],"t":259,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.481,179.676],"t":260,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[287.082,179.427],"t":261,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.764,179.227],"t":262,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.504,179.065],"t":263,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.29,178.931],"t":264,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[286.11,178.819],"t":265,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.832,178.645],"t":267,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.555,178.472],"t":270,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[285.272,178.295],"t":278,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"k":[{"s":[14.603],"t":251,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.579],"t":252,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.532],"t":253,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.45],"t":254,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.278],"t":255,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[14.082],"t":256,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.996],"t":257,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.946],"t":258,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.912],"t":259,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.887],"t":260,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.868],"t":261,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.853],"t":262,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.84],"t":263,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.83],"t":264,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.821],"t":265,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.808],"t":267,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.794],"t":270,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.78],"t":278,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Recents_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[252,157.5,0],"ix":1,"l":2},"s":{"k":[{"s":[59.97,59.97,100],"t":251,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.871,59.871,100],"t":252,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.682,59.682,100],"t":253,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[59.344,59.344,100],"t":254,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[58.64,58.64,100],"t":255,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.839,57.839,100],"t":256,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.486,57.486,100],"t":257,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.281,57.281,100],"t":258,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.142,57.142,100],"t":259,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[57.04,57.04,100],"t":260,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.961,56.961,100],"t":261,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.898,56.898,100],"t":262,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.846,56.846,100],"t":263,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.804,56.804,100],"t":264,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.768,56.768,100],"t":265,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.713,56.713,100],"t":267,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.658,56.658,100],"t":270,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[56.602,56.602,100],"t":278,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"w":504,"h":315,"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"t":277,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,-30.035,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[176.678,176.678,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"second Tasks Zoom back","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":385,"s":[98,98,100]},{"t":410,"s":[95,95,100]}],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Null :: Reposition Side Task","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-318.4,-38,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-277.34,-48.1,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,-63.25,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-111.72,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-111.197,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-109.514,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-106.268,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-100.462,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-88.39,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-74.643,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-68.591,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-65.083,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-62.7,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-60.943,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-59.584,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-58.5,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-57.616,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.886,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-56.276,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.762,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-55.328,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.96,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.648,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.385,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-54.163,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.977,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.824,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.698,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.598,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.521,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.463,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.424,0],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.963},"t":217,"s":[-84.8,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":250,"s":[0,0,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":250,"s":[302.4,189]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":255,"s":[227.56,142.34]},{"t":280,"s":[115.3,72.35]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":250,"s":[14.6]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":255,"s":[14.272]},{"t":280,"s":[13.78]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":268,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":277,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[0,-53.175,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-53.175,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":392,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":250,"s":[-411.95,20.325,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":255,"s":[-333.47,29.183,0],"to":[0,0,0],"ti":[0,0,0]},{"t":280,"s":[-215.75,42.47,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[115.3,72.35],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13.78,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":197,"op":511,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Taskbar Lofi","fr":60,"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[76,0,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[76,0,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[167,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7511","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[167,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[139,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[139,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[139,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7508","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[139,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[111,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[111,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7507","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[111,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 3","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[83,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[83,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7506","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[83,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[55,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[55,15,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 7505","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[55,15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app - 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":90,"ix":10},"p":{"k":[{"s":[36,15,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36,15,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"k":[{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5,-0.5],[5,-0.5]],"c":false}],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"divider","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":217,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":292,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-70.349,0.652,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.349,0.652,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[6.826,6.826,0],"ix":1,"l":2},"s":{"k":[{"s":[100,100,100],"t":217,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":292,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,9.501],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 12","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[2.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 11","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[9.5,2.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.185,0.148],[0,0],[0,0],[0,0],[-0.086,0.24],[0,0.271],[0.468,0.462],[0.671,0],[0.468,-0.468],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.24,0.086]],"o":[[0,0],[0,0],[0,0],[0.148,-0.185],[0.086,-0.24],[0,-0.671],[-0.462,-0.468],[-0.671,0],[-0.462,0.462],[0,0.671],[0.468,0.462],[0.271,0],[0.24,-0.086]],"v":[[0.48,0.998],[2.809,3.326],[3.326,2.809],[0.998,0.48],[1.349,-0.157],[1.478,-0.924],[0.776,-2.624],[-0.924,-3.326],[-2.633,-2.624],[-3.326,-0.924],[-2.633,0.785],[-0.924,1.478],[-0.157,1.349]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462],[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462]],"o":[[-0.32,0.32],[-0.462,0],[-0.32,-0.326],[0,-0.462],[0.326,-0.326],[0.462,0],[0.326,0.32],[0,0.462]],"v":[[0.249,0.259],[-0.924,0.739],[-2.106,0.259],[-2.587,-0.924],[-2.106,-2.097],[-0.924,-2.587],[0.249,-2.097],[0.739,-0.924]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":5,"nm":"Merge Paths 2","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0.4,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[10.326,10.326],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[91,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"k":[{"s":[182,30],"t":217,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[182,30],"t":292,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":32.672,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Taskbar Lofi","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_4","nm":"Recents_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[339.937,151.75,0],"ix":2,"l":2},"a":{"a":0,"k":[339.937,151.75,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Triangle","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[334,279],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Text field","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Sent","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Received","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[334,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":16,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82,171.125,0],"ix":2,"l":2},"a":{"a":0,"k":[82,171.125,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[82.5,140.5,0],"ix":2,"l":2},"a":{"a":0,"k":[82,140.938,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":39.375,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Search","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"header","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 6","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 5","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 3","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Message","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82,171],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"block","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 2","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Line 1","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":200,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Avatar","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"circle 1","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252,157.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"app only","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,459,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Frame 1321317559","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadAK_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,198.5,0],"ix":2,"l":2},"a":{"a":0,"k":[95,95,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":190,"h":190,"ip":53,"op":97,"st":53,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"track matte 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","tt":1,"tp":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":54,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":436,"s":[100]},{"t":439,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":4,"ix":1}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":47,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":96,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098039216,0.101960784314,0.01568627451,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":47,"op":97,"st":47,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"track matte 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Trackpad-JSON_Recents-EDU","tt":1,"tp":5,"refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,282,0],"ix":2,"l":2},"a":{"a":0,"k":[277,282,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":554,"h":564,"ip":12,"op":58,"st":-235,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"track matte 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":511,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"Trackpad-JSON_Recents-EDU","tt":1,"tp":7,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,282,0],"ix":2,"l":2},"a":{"a":0,"k":[277,282,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":554,"h":564,"ip":-217,"op":33,"st":-217,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":4,"ix":1}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"illustrations: action key","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[277,197.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":28,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":96,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4,"ix":3},"ix":3,"mn":"ADBE Vector Filter - Offset","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"frame","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":221,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}} \ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ebee1576342a..b91bfd6c9520 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -35,14 +35,14 @@
<string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstreem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Outodraai skerm"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Gee <xliff:g id="APPLICATION">%1$s</xliff:g> toegang tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
- <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Laat <xliff:g id="APPLICATION">%1$s</xliff:g> toe om by <xliff:g id="USB_DEVICE">%2$s</xliff:g> in te gaan?\nOpneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
+ <string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Laat <xliff:g id="APPLICATION">%1$s</xliff:g> toe om by <xliff:g id="USB_DEVICE">%2$s</xliff:g> in te gaan?\nOpneemtoestemming is nie aan hierdie app verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
<string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"Gee <xliff:g id="APPLICATION">%1$s</xliff:g> toegang tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"Maak <xliff:g id="APPLICATION">%1$s</xliff:g> oop om <xliff:g id="USB_DEVICE">%2$s</xliff:g> te hanteer?"</string>
<string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"Opneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel opneem. As jy <xliff:g id="APPLICATION">%1$s</xliff:g> met hierdie toestel gebruik, kan dit verhinder dat jy oproepe, kennisgewings en wekkers hoor."</string>
<string name="usb_audio_device_prompt" msgid="7944987408206252949">"As jy <xliff:g id="APPLICATION">%1$s</xliff:g> met hierdie toestel gebruik, kan dit verhinder dat jy oproepe, kennisgewings en wekkers hoor."</string>
<string name="usb_accessory_permission_prompt" msgid="717963550388312123">"Gee <xliff:g id="APPLICATION">%1$s</xliff:g> toegang tot <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>?"</string>
<string name="usb_device_confirm_prompt" msgid="4091711472439910809">"Hanteer <xliff:g id="USB_DEVICE">%2$s</xliff:g> met <xliff:g id="APPLICATION">%1$s</xliff:g>?"</string>
- <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Maak <xliff:g id="APPLICATION">%1$s</xliff:g> oop om <xliff:g id="USB_DEVICE">%2$s</xliff:g> te hanteer?\nOpneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
+ <string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"Maak <xliff:g id="APPLICATION">%1$s</xliff:g> oop om <xliff:g id="USB_DEVICE">%2$s</xliff:g> te hanteer?\nOpneemtoestemming is nie aan hierdie app verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
<string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"Hanteer <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> met <xliff:g id="APPLICATION">%1$s</xliff:g>?"</string>
<string name="usb_accessory_uri_prompt" msgid="6756649383432542382">"Geen geïnstalleerde programme werk met hierdie USB-toebehoorsel nie. Vind meer uit oor hierdie toebehoorsel by <xliff:g id="URL">%1$s</xliff:g>"</string>
<string name="title_usb_accessory" msgid="1236358027511638648">"USB-toebehoorsel"</string>
@@ -83,7 +83,7 @@
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Toestel moet ontsluit word voordat skermkiekie gestoor kan word"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer weer skermkiekie neem"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan nie skermkiekie stoor nie"</string>
- <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Die program of jou organisasie laat nie toe dat skermkiekies geneem word nie"</string>
+ <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"Die app of jou organisasie laat nie toe dat skermkiekies geneem word nie"</string>
<string name="screenshot_blocked_by_admin" msgid="5486757604822795797">"Die neem van skermskote word deur jou IT-admin geblokkeer"</string>
<string name="screenshot_edit_label" msgid="8754981973544133050">"Wysig"</string>
<string name="screenshot_edit_description" msgid="3333092254706788906">"Wysig skermkiekie"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Oudiodeling"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tik om oor te skakel of oudio te deel"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Steun oudiodeling"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Sluitskermlegstukke"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Om ’n app met ’n legstuk oop te maak, sal jy moet verifieer dat dit jy is. Hou ook in gedagte dat enigeen dit kan bekyk, selfs wanneer jou tablet gesluit is. Sommige legstukke is moontlik nie vir jou sluitskerm bedoel nie en dit kan onveilig wees om dit hier by te voeg."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Het dit"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Legstukke"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Word aan die bokant van gesprekskennisgewings en as \'n profielfoto op sluitskerm gewys, verskyn as \'n borrel, onderbreek Moenie Steur Nie"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> steun nie gesprekskenmerke nie"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Verskaf bondelterugvoer"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Hierdie kennisgewings kan nie gewysig word nie."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Oproepkennisgewings kan nie gewysig word nie."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Hierdie groep kennisgewings kan nie hier opgestel word nie"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skakel oor na app links of bo terwyl jy verdeelde skerm gebruik"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tydens verdeelde skerm: verplaas ’n app van een skerm na ’n ander"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Skuif aktiewe venster tussen skerms"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Skuif venster na links"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Skuif venster na regs"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maak venster groot"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Maak venster klein"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Invoer"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Skakel oor na volgende taal"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skakel oor na vorige taal"</string>
@@ -985,7 +995,7 @@
<string name="tuner_left" msgid="5758862558405684490">"Links"</string>
<string name="tuner_right" msgid="8247571132790812149">"Regs"</string>
<string name="tuner_menu" msgid="363690665924769420">"Kieslys"</string>
- <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g>-program"</string>
+ <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g>-app"</string>
<string name="notification_channel_alerts" msgid="3385787053375150046">"Opletberigte"</string>
<string name="notification_channel_battery" msgid="9219995638046695106">"Battery"</string>
<string name="notification_channel_screenshot" msgid="7665814998932211997">"Skermkiekies"</string>
@@ -996,8 +1006,8 @@
<string name="notification_channel_accessibility" msgid="8956203986976245820">"Toeganklikheid"</string>
<string name="instant_apps" msgid="8337185853050247304">"Kitsapps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> loop tans"</string>
- <string name="instant_apps_message" msgid="6112428971833011754">"Program is oopgemaak sonder dat dit geïnstalleer is."</string>
- <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Program is oopgemaak sonder dat dit geïnstalleer is. Tik om meer te wete te kom."</string>
+ <string name="instant_apps_message" msgid="6112428971833011754">"App is oopgemaak sonder dat dit geïnstalleer is."</string>
+ <string name="instant_apps_message_with_help" msgid="1816952263531203932">"App is oopgemaak sonder dat dit geïnstalleer is. Tik om meer inligting te kry."</string>
<string name="app_info" msgid="5153758994129963243">"Appinligting"</string>
<string name="go_to_web" msgid="636673528981366511">"Gaan na blaaier"</string>
<string name="mobile_data" msgid="4564407557775397216">"Mobiele data"</string>
@@ -1008,8 +1018,8 @@
<string name="dnd_is_off" msgid="3185706903793094463">"Moenie Steur Nie is af"</string>
<string name="dnd_is_on" msgid="7009368176361546279">"Moenie Steur Nie is aan"</string>
<string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"\'n Outomatiese reël (<xliff:g id="ID_1">%s</xliff:g>) het Moenie Steur Nie aangeskakel."</string>
- <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"\'n Program (<xliff:g id="ID_1">%s</xliff:g>) het Moenie Steur Nie aangeskakel."</string>
- <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"\'n Outomatiese reël of program het Moenie Steur Nie aangeskakel."</string>
+ <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"\'n App (<xliff:g id="ID_1">%s</xliff:g>) het Moenie Steur Nie aangeskakel."</string>
+ <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"\'n Outomatiese reël of app het Moenie Steur Nie aangeskakel."</string>
<string name="running_foreground_services_title" msgid="5137313173431186685">"Programme wat op die agtergrond loop"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"Tik vir besonderhede oor battery- en datagebruik"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Skakel mobiele data af?"</string>
@@ -1019,11 +1029,11 @@
<string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data sal nie outomaties op grond van beskikbaarheid oorskakel nie"</string>
<string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, dankie"</string>
<string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skakel oor"</string>
- <string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n program \'n toestemmingversoek verberg."</string>
+ <string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n app \'n toestemmingversoek verberg."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Dit kan inligting van <xliff:g id="APP">%1$s</xliff:g> af lees"</string>
<string name="slice_permission_text_2" msgid="6758906940360746983">"– Dit kan handelinge binne <xliff:g id="APP">%1$s</xliff:g> uitvoer"</string>
- <string name="slice_permission_checkbox" msgid="4242888137592298523">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om skyfies uit enige program te gebruik"</string>
+ <string name="slice_permission_checkbox" msgid="4242888137592298523">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om skyfies uit enige app te gebruik"</string>
<string name="slice_permission_allow" msgid="6340449521277951123">"Laat toe"</string>
<string name="slice_permission_deny" msgid="6870256451658176895">"Weier"</string>
<string name="auto_saver_title" msgid="6873691178754086596">"Tik om Batterybespaarder te skeduleer"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gebruik minder as <xliff:g id="LENGTH">%1$d</xliff:g> karakters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Bounommer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Bounommer is na knipbord gekopieer."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopieer na knipbord."</string>
<string name="basic_status" msgid="2315371112182658176">"Maak gesprek oop"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Gespreklegstukke"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tik op \'n gesprek om dit by jou tuisskerm te voeg"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Keer die foon om vir hoër resolusie"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Voorste skerm is aangeskakel"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gevou"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"oopgevou"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Stelselkontroles"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Stelselapps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Verrigting van veelvuldige take"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Onlangse apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Verdeelde skerm"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Invoer"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Appkortpaaie"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toeganklikheid"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Kortpadsleutels"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pasmaak kortpadsleutels"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Druk sleutel om kortpad toe te wys"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Verwyder kortpad?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Druk sleutel om kortpad toe te wys"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dit sal jou gepasmaakte kortpad permanent uitvee."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Soekkortpaaie"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen soekresultate nie"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Vou ikoon in"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikoon vir Handeling- of Meta-sleutel"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plusikoon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Pasmaak"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klaar"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vou ikoon uit"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"vorentoe-skuinsstreep"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sleephandvatsel"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Sleutelbordinstellings"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Stel kortpad"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Verwyder"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Kanselleer"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Druk sleutel"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander sleutel."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander sleutel."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kortpad kan nie gestel word nie."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeer met jou sleutelbord"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer kortpadsleutels"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeer met jou raakpaneel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 0351c7508703..cf73b71168b9 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ብሉቱዝን ይጠቀሙ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ተገናኝቷል"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"የድምጽ ማጋራት"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ኦዲዮ ለመቀየር ወይም ለማጋራት መታ ያድርጉ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"የድምፅ ማጋራትን ይደግፋል"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ተቀምጧል"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"የማያ ገፅ ቁልፍ ምግብሮች"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ምግብር በመጠቀም መተግበሪያ ለመክፈት እርስዎ መሆንዎን ማረጋገጥ አለብዎት። እንዲሁም የእርስዎ ጡባዊ በተቆለፈበት ጊዜ እንኳን ማንኛውም ሰው እነሱን ማየት እንደሚችል ከግምት ውስጥ ያስገቡ። አንዳንድ ምግብሮች ለማያ ገፅ ቁልፍዎ የታሰቡ ላይሆኑ ይችላሉ እና እዚህ ለማከል አስተማማኝ ላይሆኑ ይችላሉ።"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ገባኝ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ምግብሮች"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"በውይይት ማሳወቂያዎች አናት ላይ እና በማያ ገፅ መቆለፊያ ላይ እንደ መገለጫ ምስል ይታያል፣ እንደ አረፋ ሆኖ ይታያል፣ አትረብሽን ያቋርጣል"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ቅድሚያ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> የውይይት ባህሪያትን አይደግፍም"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"የቅርቅብ ግብረመልስ አቅርብ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"እነዚህ ማሳወቂያዎች ሊሻሻሉ አይችሉም።"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"የጥሪ ማሳወቂያዎች ሊቀየሩ አይችሉም።"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"የማሳወቂያዎች ይህ ቡድን እዚህ ላይ ሊዋቀር አይችልም"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"የተከፈለ ማያ ገጽን ሲጠቀሙ በቀኝ ወይም ከላይ ወዳለ መተግበሪያ ይቀይሩ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"በተከፈለ ማያ ገጽ ወቅት፡- መተግበሪያን ከአንዱ ወደ ሌላው ተካ"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"በማሳያዎች መካከል ንቁ መስኮትን ያንቀሳቅሱ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"መስኮትን ወደ ግራ አሳንስ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"መስኮትን ወደ ቀኝ አሳንስ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"መስኮትን አሳድግ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"መስኮት አሳንስ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ግቤት"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ወደ ቀጣዩ ቋንቋ ቀይር"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ወደ ቀዳሚ ቋንቋ ቀይር"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ከ<xliff:g id="LENGTH">%1$d</xliff:g> የሚያንሱ ቁምፊዎችን ይጠቀሙ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"የግንብ ቁጥር"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"የገንባ ቁጥር ወደ ቅንጥብ ሰሌዳ ተቀድቷል።"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ወደ ቅንጥብ ሰሌዳ ቅዳ።"</string>
<string name="basic_status" msgid="2315371112182658176">"ውይይት ይክፈቱ"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"የውይይት ምግብሮች"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"በመነሻ ማያ ገጽዎ ላይ ለማከል አንድ ውይይት መታ ያድርጉ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ለከፍተኛ ጥራት ስልኩን ይቀይሩ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"የፊት ለፊት ማያ ገፅ በርቷል"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"የታጠፈ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"የተዘረጋ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"የሥርዓት መቆጣጠሪያዎች"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"የሥርዓት መተግበሪያዎች"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ብዙ ተግባራትን በተመሳሳይ ጊዜ ማከናወን"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"የቅርብ ጊዜ መተግበሪያዎች"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"የተከፈለ ማያ ገፅ"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ግብዓት"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"የመተግበሪያ አቋራጮች"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ተደራሽነት"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"የቁልፍ ሰሌዳ አቋራጮች"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"የቁልፍ ሰሌዳ አቋራጮችን ያብጁ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"አቋራጭ ለመመደብ ቁልፍ ይጫኑ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"አቋራጭ ይወገድ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"አቋራጭ ለመመደብ ቁልፍ ይጫኑ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ይህ ብጁ አቋራጭዎን በቋሚነት ይሰርዛል።"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"የፍለጋ አቋራጮች"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ምንም የፍለጋ ውጤቶች የሉም"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"መሰብሰቢያ አዶ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"የእርምጃ ወይም ሜታ ቁልፍ አዶ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"የመደመር አዶ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"አብጅ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ተከናውኗል"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"መዘርጊያ አዶ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ወይም"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ሲደመር"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ወደፊት ህዝባር"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"መያዣ ይጎትቱ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"የቁልፍ ሰሌዳ ቅንብሮች"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"አቋራጭ አቀናብር"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"አስወግድ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ይቅር"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ቁልፍ ይጫኑ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"የቁልፍ ጥምረት አስቀድሞ በሥራ ላይ ነው። ሌላ ቁልፍ ይሞክሩ።"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"የቁልፍ ጥምረት አስቀድሞ በሥራ ላይ ነው። ሌላ ቁልፍ ይሞክሩ።"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"አቋራጩ ሊቀናበር አይችልም።"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"የቁልፍ ሰሌዳዎን በመጠቀም ያስሱ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"የቁልፍ ሰሌዳ አቋራጮችን ይወቁ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"የመዳሰሻ ሰሌዳዎን በመጠቀም ያስሱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 645d88008b21..4ff613163294 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -113,7 +113,7 @@
<string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"تسجيل شاشة تطبيق واحد"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"تسجيل الشاشة بكاملها"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"‏تسجيل محتوى الشاشة بالكامل: %s"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك، لذا يُرجى توخي الحذر بشأن المعلومات الظاهرة، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور والمقاطع الصوتية والفيديوهات."</string>
<string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"سيتم تسجيل كل المحتوى المعروض أو المشغَّل على شاشة التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات الظاهرة، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور والمقاطع الصوتية والفيديوهات."</string>
<string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"تسجيل الشاشة"</string>
<string name="screenrecord_app_selector_title" msgid="3854492366333954736">"يُرجى اختيار تطبيق لتسجيل شاشته"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"استخدام البلوتوث"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متّصل"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"مشاركة الصوت"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"انقر لتبديل مصدر الصوت أو مشاركته"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"تتوفّر ميزة \"مشاركة الصوت\""</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"التطبيقات المصغّرة المصمَّمة لشاشة القفل"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"لفتح تطبيق باستخدام تطبيق مصغَّر، عليك إثبات هويتك. يُرجى ملاحظة أنّ أي شخص يمكنه الاطّلاع محتوى التطبيقات المصغَّرة، حتى وإن كان جهازك اللوحي مُقفلاً. بعض التطبيقات المصغّرة قد لا تكون مُصمَّمة لإضافتها إلى شاشة القفل، وقد يكون هذا الإجراء غير آمن."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"حسنًا"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"التطبيقات المصغَّرة"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
@@ -695,10 +700,10 @@
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. انقر للتجاهل. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. انقر للتعيين على الاهتزاز."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. انقر لكتم الصوت."</string>
- <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكُّم في مستوى الضوضاء"</string>
+ <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكّم بالضوضاء"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"الصوت المكاني"</string>
- <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"غير مفعّل"</string>
- <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"تفعيل"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"عدم التفعيل"</string>
+ <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"التفعيل بدون تتبّع"</string>
<string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"تتبُّع حركة الرأس"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"انقر لتغيير وضع الرنين."</string>
<string name="volume_ringer_mode" msgid="6867838048430807128">"وضع الرنين"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"تظهر في أعلى إشعارات المحادثات وكصورة ملف شخصي على شاشة القفل وتظهر على شكل فقاعة لمقاطعة ميزة \"عدم الإزعاج\"."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"الأولوية"</string>
<string name="no_shortcut" msgid="8257177117568230126">"لا يدعم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> ميزات المحادثات."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"تقديم ملاحظات مُجمّعة"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"يتعذّر تعديل هذه الإشعارات."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"لا يمكن تعديل إشعارات المكالمات."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"يتعذّر ضبط مجموعة الإشعارات هذه هنا."</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"التبديل إلى التطبيق على اليمين أو الأعلى أثناء استخدام \"تقسيم الشاشة\""</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"استبدال تطبيق بآخر في وضع \"تقسيم الشاشة\""</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"نقل نافذة نشطة بين شاشات العرض"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"نقل النافذة إلى اليمين"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"نقل النافذة إلى اليسار"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"تكبير النافذة"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"تصغير النافذة"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"الإدخال"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"التبديل إلى اللغة التالية"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"التبديل إلى اللغة السابقة"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"يجب استخدام أقل من <xliff:g id="LENGTH">%1$d</xliff:g> حرف."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"رقم الإصدار"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"تم نسخ رقم الإصدار إلى الحافظة."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"نسخ المحتوى إلى الحافظة"</string>
<string name="basic_status" msgid="2315371112182658176">"محادثة مفتوحة"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"التطبيقات المصغّرة للمحادثات"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"انقر على محادثة لإضافتها إلى \"الشاشة الرئيسية\""</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"للحصول على درجة دقة أعلى، اقلِب الهاتف."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"تم تفعيل الشاشة الأمامية"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"مطوي"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"غير مطوي"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‫%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"عناصر التحكّم في النظام"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"تطبيقات النظام"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"تعدُّد المهام"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"التطبيقات المستخدمة مؤخرًا"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"تقسيم الشاشة"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"الإدخال"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"اختصارات التطبيقات"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"تسهيل الاستخدام"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"اختصارات لوحة المفاتيح"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"تخصيص اختصارات لوحة المفاتيح"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"اضغط على مفتاح لتخصيص الاختصار"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"هل تريد إزالة هذا الاختصار؟"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"اضغط على مفتاح لتخصيص الاختصار"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"سيؤدي هذا الإجراء إلى حذف الاختصار المخصّص نهائيًا."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"البحث في الاختصارات"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ما مِن نتائج بحث"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"رمز التصغير"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"‏رمز مفتاح الإجراء (مفتاح Meta)"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"رمز علامة الجمع (+)"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"تخصيص"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"تم"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"رمز التوسيع"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"أو"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"زائد"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"شرطة مائلة للأمام"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"مقبض السحب"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"إعدادات لوحة المفاتيح"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ضبط الاختصار"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"إزالة"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"إلغاء"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"اضغط على مفتاح"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مفتاح آخر."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مفتاح آخر."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"تعذَّر ضبط الاختصار."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"التنقّل باستخدام لوحة المفاتيح"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"تعرَّف على اختصارات لوحة المفاتيح"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"التنقّل باستخدام لوحة اللمس"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index e14f4ccdece6..f91a3b011c5d 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যৱহাৰ কৰক"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"সংযুক্ত আছে"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"অডিঅ’ শ্বেয়াৰ কৰা"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"অডিঅ’ সলনি কৰিবলৈ বা শ্বেয়াৰ কৰিলৈ টিপক"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ছেভ কৰা হৈছে"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"লক স্ক্ৰীন ৱিজেট"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"এটা ৱিজেট ব্যৱহাৰ কৰি কোনো এপ্ খুলিবলৈ, এয়া আপুনিয়েই বুলি সত্যাপন পৰীক্ষা কৰিব লাগিব। লগতে, মনত ৰাখিব যে যিকোনো লোকেই সেইবোৰ চাব পাৰে, আনকি আপোনাৰ টেবলেটটো লক হৈ থাকিলেও। কিছুমান ৱিজেট হয়তো আপোনাৰ লক স্ক্ৰীনৰ বাবে কৰা হোৱা নাই আৰু ইয়াত যোগ কৰাটো অসুৰক্ষিত হ’ব পাৰে।"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"বুজি পালোঁ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ৱিজেট"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"বাৰ্তালাপৰ জাননীৰ শীৰ্ষত আৰু প্ৰ’ফাইল চিত্ৰ হিচাপে লক স্ক্ৰীনত দেখুৱায়, এটা বাবল হিচাপে দেখা পোৱা যায়, অসুবিধা নিদিব ম’ডত ব্যাঘাত জন্মায়"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"অগ্ৰাধিকাৰ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বাৰ্তালাপৰ সুবিধাসমূহ সমৰ্থন নকৰে"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"বাণ্ডল হিচাপে থকা জাননীত মতামত প্ৰদান কৰক"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"এই জাননীসমূহ সংশোধন কৰিব নোৱাৰি।"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"কলৰ জাননীসমূহ সংশোধন কৰিব নোৱাৰি।"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"এই ধৰণৰ জাননীবোৰ ইয়াত কনফিগাৰ কৰিব পৰা নাযায়"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰাৰ সময়ত বাওঁফালে অথবা ওপৰত থকা এপলৈ সলনি কৰক"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"বিভাজিত স্ক্ৰীনৰ ব্যৱহাৰ কৰাৰ সময়ত: কোনো এপ্ এখন স্ক্ৰীনৰ পৰা আনখনলৈ নিয়ক"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ডিছপ্লে’সমূহৰ মাজত সক্রিয় হৈ থকা ৱিণ্ড’ সলনা সলনিকৈ ব্যৱহাৰ কৰক"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ৱিণ্ড’ বাওঁফাললৈ স্থানান্তৰ কৰক"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ৱিণ্ড’ সোঁফাললৈ স্থানান্তৰ কৰক"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ৱিণ্ড’ মেক্সিমাইজ কৰক"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ৱিণ্ড’ মিনিমাইজ কৰক"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ইনপুট"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"পৰৱৰ্তী ভাষাটোলৈ সলনি কৰক"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"পূৰ্বৰ ভাষালৈ সলনি কৰক"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> টাতকৈ কম বৰ্ণ ব্যৱহাৰ কৰক"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ডৰ নম্বৰ"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ক্লিপব’ৰ্ডলৈ বিল্ডৰ নম্বৰ প্ৰতিলিপি কৰা হ’ল।"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ক্লিপব\'ৰ্ডলৈ প্ৰতিলিপি কৰক।"</string>
<string name="basic_status" msgid="2315371112182658176">"বাৰ্তালাপ খোলক"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"বাৰ্তালাপ ৱিজেট"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"আপোনাৰ গৃহ স্ক্ৰীনত কোনো বাৰ্তালাপ যোগ দিবলৈ সেইটোত টিপক"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"অধিক ৰিজ’লিউশ্বনৰ বাবে, ফ’নটো লুটিয়াই দিয়ক"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"সন্মুখৰ স্ক্ৰীনখন অন কৰা হৈছে"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ফ’ল্ড কৰা"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"আনফ’ল্ড কৰা"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ছিষ্টেমৰ নিয়ন্ত্ৰণ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ছিষ্টেম এপ্‌"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"মাল্টিটাস্কিং"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"শেহতীয়া এপ্‌সমূহ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"বিভাজিত স্ক্ৰীন"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ইনপুট"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"এপ্ শ্বৰ্টকাটসমূহ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"সাধ্য সুবিধা"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"কীব’ৰ্ডৰ শ্বৰ্টকাট"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"কীব’ৰ্ডৰ শ্বৰ্টকাট কাষ্টমাইজ কৰক"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"শ্বৰ্টকাটৰ ভূমিকা অৰ্পণ কৰিবলৈ কী টিপক"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শ্বৰ্টকাট আঁতৰাবনে?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শ্বৰ্টকাটৰ ভূমিকা অৰ্পণ কৰিবলৈ কী টিপক"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এইটোৱে আপোনাৰ কাষ্টম শ্বৰ্টকাট মচিব।"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সন্ধানৰ শ্বৰ্টকাট"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"সন্ধানৰ কোনো ফলাফল নাই"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"সংকোচন কৰাৰ চিহ্ন"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"কাৰ্য বা মেটা কীৰ চিহ্ন"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"যোগ চিনৰ চিহ্ন"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"কাষ্টমাইজ কৰক"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"হ’ল"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"বিস্তাৰ কৰাৰ চিহ্ন"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"যোগ চিন"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ফৰৱাৰ্ড শ্লেশ্ব"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ড্ৰেগ হেণ্ডেল"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"কীব’ৰ্ডৰ ছেটিং"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"শ্বৰ্টকাট ছেট কৰক"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"আঁতৰাওক"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"বাতিল কৰক"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"কী টিপক"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা কী ব্যৱহাৰ কৰি চাওক।"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা কী ব্যৱহাৰ কৰি চাওক।"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শ্বৰ্টকাট ছেট কৰিব নোৱাৰি।"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"কীব’ৰ্ড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীব’ৰ্ডৰ শ্বৰ্টকাটসমূহৰ বিষয়ে জানক"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপোনাৰ টাচ্চপেড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 90168703b896..84299dc67ec0 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-u açın"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio paylaşma"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dəyişmək və ya audio paylaşmaq üçün toxunun"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audio paylaşma dəstəklənir"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Kilid ekranı vidcetləri"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Vidcetdən istifadə edərək tətbiqi açmaq üçün kimliyi doğrulamalısınız. Planşet kilidli olsa da, hər kəs vidcetlərə baxa bilər. Bəzi vidcetlər kilid ekranı üçün nəzərdə tutulmayıb və bura əlavə etmək təhlükəli ola bilər."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Anladım"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidcetlər"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Söhbət bildirişlərinin yuxarısında və kilid ekranında profil şəkli kimi göstərilir, baloncuq kimi görünür, Narahat Etməyin rejimini kəsir"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> söhbət funksiyalarını dəstəkləmir"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Paket rəyi təmin edin"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirişlər dəyişdirilə bilməz."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Zəng bildirişləri dəyişdirilə bilməz."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Bu bildiriş qrupunu burada konfiqurasiya etmək olmaz"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran istifadə edərkən solda və ya yuxarıda tətbiqə keçin"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran rejimində: tətbiqi birindən digərinə dəyişin"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktiv pəncərəni displeylər arasında hərəkət etdirin"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Pəncərəni sola hərəkət etdirin"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Pəncərəni sağa hərəkət etdirin"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Pəncərəni böyüdün"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Pəncərəni minimallaşdırın"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Daxiletmə"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Növbəti dilə keçin"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Əvvəlki dilə keçin"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Maksimum <xliff:g id="LENGTH">%1$d</xliff:g> simvol istifadə edin"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Montaj nömrəsi"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Versiya nömrəsi mübadilə buferinə kopyalandı."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"mübadilə buferinə kopiyalayın."</string>
<string name="basic_status" msgid="2315371112182658176">"Açıq söhbət"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Söhbət vidcetləri"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Əsas ekranınıza əlavə etmək üçün söhbətə toxunun"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksək ayırdetmə dəqiqliyi üçün telefonu çevirin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ön ekran aktiv edildi"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"qatlanmış"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"açıq"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistem nizamlayıcıları"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistem tətbiqləri"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Çoxsaylı tapşırıq icrası"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Son tətbiqlər"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Bölünmüş ekran"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Daxiletmə"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Tətbiq qısayolları"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Xüsusi imkanlar"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatura qısayolları"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klaviatura qısayollarını fərdiləşdirin"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Qısayol təyin etmək üçün düyməni basın"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Qısayol silinsin?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Qısayol təyin etmək üçün düyməni basın"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bu, fərdi qısayolunuzu həmişəlik siləcək."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Axtarış qısayolları"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Axtarış nəticəsi yoxdur"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"İkonanı yığcamlaşdırın"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Əməliyyat və ya Meta düyməsi ikonası"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Üstəgəl ikonası"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Fərdiləşdirin"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hazırdır"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"İkonanı genişləndirin"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"və ya"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"irəli sləş"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dəstəyi çəkin"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatura ayarları"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Qısayol ayarlayın"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Silin"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Ləğv edin"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Düyməni basın"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Düymə kombinasiyası artıq istifadə olunur. Başqa düyməni sınayın."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Düymə kombinasiyası artıq istifadə olunur. Başqa düyməni sınayın."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Qısayol ayarlana bilməz."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviaturadan istifadə edərək hərəkət edin"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klaviatura qısayolları haqqında öyrənin"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Taçpeddən istifadə edərək hərəkət edin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index d2636c050db0..bc385a10160f 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvuka"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite da biste prebacili ili delili zvuk"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava deljenje zvuka"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Vidžeti za zaključani ekran"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da biste otvorili aplikaciju koja koristi vidžet, treba da potvrdite da ste to vi. Imajte u vidu da svako može da ga vidi, čak i kada je tablet zaključan. Neki vidžeti možda nisu namenjeni za zaključani ekran i možda nije bezbedno da ih tamo dodate."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Važi"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidžeti"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se u vrhu obaveštenja o konverzacijama i kao slika profila na zaključanom ekranu, pojavljuje se kao oblačić, prekida režim Ne uznemiravaj"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava funkcije konverzacije"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pružite povratne informacije o skupu"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ova obaveštenja ne mogu da se menjaju."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obaveštenja o pozivima ne mogu da se menjaju."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ova grupa obaveštenja ne može da se konfiguriše ovde"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju sleva ili iznad dok koristite podeljeni ekran"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"U režimu podeljenog ekrana: zamena jedne aplikacije drugom"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premesti aktivan prozor na sledeći ekran"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Pomerite prozor nalevo"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Pomerite prozor nadesno"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Povećajte prozor"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Smanjite prozor"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Pređi na sledeći jezik"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Pređi na prethodni jezik"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Koristite manji broj znakova od <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u privremenu memoriju."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopirajte u privremenu memoriju."</string>
<string name="basic_status" msgid="2315371112182658176">"Otvorite konverzaciju"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Vidžeti za konverzaciju"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite konverzaciju da biste je dodali na početni ekran"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za veću rezoluciju obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji ekran je uključen"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistemske kontrole"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistemske aplikacije"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Obavljanje više zadataka istovremeno"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nedavne aplikacije"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Podeljeni ekran"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečice za aplikacije"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tasterske prečice"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodite tasterske prečice"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite taster da biste dodelili prečicu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite da uklonite prečicu?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite taster da biste dodelili prečicu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovim ćete trajno izbrisati prilagođenu prečicu."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pretražite prečice"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretrage"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za skupljanje"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona tastera za radnju ili meta tastera"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona znaka plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Prilagodi"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"kosa crta unapred"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za prevlačenje"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Podešavanja tastature"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Podesi prečicu"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Otkaži"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite taster"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tastera se već koristi. Probajte sa drugim tasterom."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tastera se već koristi. Probajte sa drugim tasterom."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Podešavanje prečice nije uspelo."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tasterskim prečicama"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću tačpeda"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 388950cb7bdd..a5f4da95a892 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Выкарыстоўваць Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Падключана"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Абагульванне аўдыя"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Націсніце, каб пераключыць або абагуліць аўдыя"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Падтрымліваецца абагульванне аўдыя"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Захавана"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Віджэты на экране блакіроўкі"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Каб адкрыць праграму з дапамогай віджэта, вам неабходна будзе пацвердзіць сваю асобу. Таксама памятайце, што такія віджэты могуць пабачыць іншыя людзі, нават калі экран планшэта заблакіраваны. Некаторыя віджэты могуць не падыходзіць для выкарыстання на экране блакіроўкі, і дадаваць іх сюды можа быць небяспечна."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Зразумела"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Віджэты"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"З’яўляецца ўверсе раздзела размоў як усплывальнае апавяшчэнне, якое перарывае рэжым \"Не турбаваць\" і паказвае на экране блакіроўкі відарыс профілю"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Прыярытэт"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не падтрымлівае функцыі размовы"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пакінуць групавы водгук"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Гэтыя апавяшчэнні нельга змяніць."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Апавяшчэнні пра выклікі нельга змяніць."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Тут канфігурыраваць гэту групу апавяшчэнняў забаронена"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пераключыцца на праграму злева або ўверсе на падзеленым экране"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"У рэжыме падзеленага экрана замяніць адну праграму на іншую"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Перамясціць актыўнае акно паміж дысплэямі"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Перамясціць акно ўлева"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Перамясціць акно ўправа"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Разгарнуць акно"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Згарнуць акно"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Увод"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Пераключыцца на наступную мову"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Пераключыцца на папярэднюю мову"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Колькасць сімвалаў павінна быць меншай за <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Нумар зборкі"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Нумар зборкі скапіраваны ў буфер абмену."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"скапіраваць у буфер абмену."</string>
<string name="basic_status" msgid="2315371112182658176">"Адкрытая размова"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Віджэты размовы"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Націсніце на размову, каб дадаць яе на галоўны экран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Каб зрабіць фота з больш высокай раздзяляльнасцю, павярніце тэлефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Пярэдні экран уключаны"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"складзена"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"раскладзена"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Элементы кіравання сістэмай"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Сістэмныя праграмы"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Шматзадачнасць"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Нядаўнія праграмы"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Падзелены экран"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Увод"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Ярлыкі праграм"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Спецыяльныя магчымасці"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Спалучэнні клавіш"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Наладзіць спалучэнні клавіш"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Націсніце клавішу, каб прызначыць спалучэнне клавіш"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Выдаліць спалучэнне клавіш?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Націсніце клавішу, каб прызначыць спалучэнне клавіш"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Гэта дзеянне назаўсёды выдаліць прызначанае вамі спалучэнне клавіш."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук спалучэнняў клавіш"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма вынікаў пошуку"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Згарнуць\""</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Значок клавішы дзеяння (мета-клавішы)"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Значок плюса"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Наладзіць"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Гатова"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Разгарнуць\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"+"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"касая рыса ўправа"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перацягвання"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налады клавіятуры"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Наладзіць спалучэнне клавіш"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Выдаліць"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Скасаваць"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Націсніце клавішу"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншую клавішу."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншую клавішу."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не ўдаецца наладзіць спалучэнне клавіш."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігацыя з дапамогай клавіятуры"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Азнаёмцеся са спалучэннямі клавіш"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігацыя з дапамогай сэнсарнай панэлі"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 2d68f9190a30..e36cf3098117 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Използване на Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Установена е връзка"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Споделяне на звука"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Докоснете, за да превключите или споделите аудио"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддържа споделяне на звука"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Запазено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Приспособления за заключения екран"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"За да отворите дадено приложение посредством приспособление, ще трябва да потвърдите, че това сте вие. Също така имайте предвид, че всеки ще вижда приспособленията дори когато таблетът ви е заключен. Възможно е някои от тях да не са предназначени за заключения екран и добавянето им на него може да е опасно."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Разбрах"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Приспособления"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Показва се в горната част на известията за разговори и като снимка на потребителския профил на заключения екран, изглежда като балонче, прекъсва режима „Не безпокойте“"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддържа функциите за разговор"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Предоставяне на отзиви за пакета"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Тези известия не могат да бъдат променяни."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Известията за обаждания не могат да бъдат променяни."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Тази група от известия не може да бъде конфигурирана тук"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Превключване към приложението вляво/отгоре в режима на разделен екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"При разделен екран: замяна на дадено приложение с друго"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Преместване на активния прозорец между екраните"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Преместване на прозореца наляво"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Преместване на прозореца надясно"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Увеличаване на прозореца"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Намаляване на прозореца"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Въвеждане"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Превключване към следващия език"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Превключване към предишния език"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Използвайте по-малко от <xliff:g id="LENGTH">%1$d</xliff:g> знака"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер на компилацията"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Номерът на компилацията е копиран в буферната памет."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копиране в буферната памет."</string>
<string name="basic_status" msgid="2315371112182658176">"Отворен разговор"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Приспособления за разговор"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Докоснете разговор, за да го добавите към началния си екран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За по-висока разделителна способност обърнете телефона"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предният екран е включен"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Системни контроли"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системни приложения"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Няколко задачи едновременно"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Скорошни приложения"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Разделен екран"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Въвеждане"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Преки пътища към приложения"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Достъпност"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Клавишни комбинации"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Персонализиране на клавишните комбинации"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Натиснете клавиш, за да зададете клавишна комбинация"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се премахне ли клавишната комбинация?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натиснете клавиш, за да зададете клавишна комбинация"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Това ще изтрие персонализираната клавишна комбинация за постоянно."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Търсете клавишни комбинации"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма резултати от търсенето"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за свиване"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Икона на клавиша за действия или клавиша Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Икона на плюс"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Персонализиране"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за разгъване"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"плюс"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"наклонена черта"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Манипулатор за преместване с плъзгане"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Настройки на клавиатурата"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Задаване на клавишна комбинация"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Премахване"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Отказ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Натиснете клавиш"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Клавишната комбинация вече се използва. Опитайте с друг клавиш."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Клавишната комбинация вече се използва. Опитайте с друг клавиш."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Прекият път не може да се зададе."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигирайте посредством клавиатурата си"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете за клавишните комбинации"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигирайте посредством сензорния панел"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 4281ea111b03..8f5effc01982 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যবহার করুন"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"কানেক্ট করা আছে"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"অডিও শেয়ারিং"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"অডিও শেয়ার বা পরিবর্তন করতে ট্যাপ করুন"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"\'অডিও শেয়ারিং\' ফিচার কাজ করে"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"লক স্ক্রিন উইজেট"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"উইজেট ব্যবহার করে কোনও অ্যাপ খুলতে, আপনাকে নিজের পরিচয় যাচাই করতে হবে। এছাড়াও, মনে রাখবেন, আপনার ট্যাবলেট লক থাকলেও যেকেউ তা দেখতে পারবেন। কিছু উইজেট আপনার লক স্ক্রিনের উদ্দেশ্যে তৈরি করা হয়নি এবং এখানে যোগ করা নিরাপদ নাও হতে পারে।"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"বুঝেছি"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"উইজেট"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"কথোপকথনের বিজ্ঞপ্তির উপরের দিকে এবং প্রোফাইল ছবি হিসেবে লক স্ক্রিনে দেখানো হয়, বাবল হিসেবেও এটি দেখা যায় এবং এর ফলে \'বিরক্ত করবে না\' মোডে কাজ করতে অসুবিধা হয়"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"অগ্রাধিকার"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এ কথোপকথন ফিচার কাজ করে না"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"বান্ডেল সম্পর্কে মতামত দিন"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"এই বিজ্ঞপ্তিগুলি পরিবর্তন করা যাবে না।"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"কল বিজ্ঞপ্তি পরিবর্তন করা যাবে না।"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"এই সমস্ত বিজ্ঞপ্তিকে এখানে কনফিগার করা যাবে না"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"স্প্লিট স্ক্রিন ব্যবহার করার সময় বাঁদিকের বা উপরের অ্যাপে পাল্টে নিন"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"\'স্প্লিট স্ক্রিন\' থাকাকালীন: একটি অ্যাপ থেকে অন্যটিতে পাল্টান"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ডিসপ্লের মধ্যে একটি থেকে অপরটিতে অ্যাক্টিভ উইন্ডোটি সরান"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"উইন্ডো বাঁদিকে সরান"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"উইন্ডো ডানদিকে সরান"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"উইন্ডো বড় করুন"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"উইন্ডো ছোট করুন"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ইনপুট"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"পরবর্তী ভাষায় পাল্টান"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"আগের ভাষায় পাল্টান"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>টির চেয়ে কম অক্ষর ব্যবহার করুন"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ড নম্বর"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"বিল্ড নম্বর ক্লিপবোর্ডে কপি করা হয়েছে।"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ক্লিপবোর্ডে কপি করুন।"</string>
<string name="basic_status" msgid="2315371112182658176">"খোলা কথোপকথন"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"কথোপকথন উইজেট"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"কোনও কথোপথন আপনার হোম স্ক্রিনে যোগ করার জন্য এতে ট্যাপ করুন"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"আরও বেশি রেজোলিউশনের জন্য, ফোন ফ্লিপ করুন"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ফ্রন্ট স্ক্রিন চালু আছে"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ফোল্ড করা রয়েছে"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ফোল্ড করা নেই"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"সিস্টেম কন্ট্রোল"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"সিস্টেম অ্যাপ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"মাল্টিটাস্কিং"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"সম্প্রতি ব্যবহার করা অ্যাপ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"স্প্লিট স্ক্রিন"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ইনপুট"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"অ্যাপ শর্টকাট"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"অ্যাক্সেসিবিলিটি"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"কীবোর্ড শর্টকাট"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"কীবোর্ড শর্টকাট কাস্টমাইজ করুন"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"শর্টকাট অ্যাসাইন করতে কী প্রেস করুন"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শর্টকাট সরাবেন?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শর্টকাট অ্যাসাইন করতে কী প্রেস করুন"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এটি আপনার কাস্টম শর্টকাট স্থায়ীভাবে মুছে ফেলবে।"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"শর্টকাট সার্চ করুন"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"কোনও সার্চ ফলাফল নেই"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"আইকন আড়াল করুন"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"অ্যাকশন বা মেটা কী আইকন"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"প্লাস আইকন"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"কাস্টমাইজ করুন"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"হয়ে গেছে"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"আইকন বড় করুন"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"যোগ চিহ্ন"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ফরওয়ার্ড স্ল্যাশ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"টেনে আনার হ্যান্ডেল"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"কীবোর্ড সেটিংস"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"শর্টকাট সেট করুন"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"সরান"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"বাতিল করুন"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"কী প্রেস করুন"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"কী কম্বিনেশন আগে থেকে ব্যবহার হচ্ছে। অন্য কী ব্যবহার করে দেখুন।"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কী কম্বিনেশন আগে থেকে ব্যবহার হচ্ছে। অন্য কী ব্যবহার করে দেখুন।"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শর্টকাট সেট করা যায়নি।"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"আপনার কীবোর্ড ব্যবহার করে নেভিগেট করুন"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীবোর্ড শর্টকাট সম্পর্কে জানুন"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপনার টাচপ্যাড ব্যবহার করে নেভিগেট করুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 9d9bf7f004a2..0a88d447b202 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Dijeljenje zvuka"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite da uključite ili dijelite zvučni zapis"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava dijeljenje zvuka"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Vidžeti na zaključanom ekranu"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da otvorite aplikaciju pomoću vidžeta, morat ćete potvrditi identitet. Također imajte na umu da ih svako može pregledati, čak i ako je tablet zaključan. Neki vidžeti možda nisu namijenjeni za vaš zaključani ekran i njihovo dodavanje ovdje možda nije sigurno."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Razumijem"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidžeti"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se na vrhu obavještenja u razgovorima i kao slika profila na zaključanom ekranu, izgleda kao oblačić, prekida funkciju Ne ometaj"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava funkcije razgovora"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošaljite povratne informacije o paketu"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ta obavještenja se ne mogu izmijeniti."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Nije moguće izmijeniti obavještenja o pozivima."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ovu grupu obavještenja nije moguće konfigurirati ovdje"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju lijevo ili iznad dok koristite podijeljeni ekran"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Za vrijeme podijeljenog ekrana: zamjena jedne aplikacije drugom"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premještanje aktivnog prozora između ekrana"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Pomicanje prozora ulijevo"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Pomicanje prozora udesno"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimiziranje prozora"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimiziranje prozora"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Prebacivanje na sljedeći jezik"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prebacivanje na prethodni jezik"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Koristite manje od <xliff:g id="LENGTH">%1$d</xliff:g> znak(ov)a"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u međumemoriju."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje u međumemoriju."</string>
<string name="basic_status" msgid="2315371112182658176">"Otvoreni razgovor"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Vidžeti razgovora"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite razgovor da ga dodate na početni ekran"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu rezoluciju obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji ekran je uključen"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sklopljeno"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otklopljeno"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistemske kontrole"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistemske aplikacije"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nedavne aplikacije"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Podijeljeni ekran"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečice aplikacije"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Prečice tastature"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodite prečice na tastaturi"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipku da dodijelite prečicu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ukloniti prečicu?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da dodijelite prečicu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovo će trajno izbrisati prilagođenu prečicu."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečica pretraživanja"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sužavanja"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona tipke radnji ili meta tipka"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona znaka plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Prilagođavanje"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona proširivanja"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"kosa crta"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ručica za prevlačenje"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Postavke tastature"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Postavi prečicu"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Otkaži"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipku"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ta se kombinacija tipki već koristi. Pokušajte s drugom tipkom."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ta se kombinacija tipki već koristi. Pokušajte s drugom tipkom."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Prečica se ne može postaviti."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o prečicama tastature"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 0be3d80edcb7..614fe233c965 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza el Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartició d\'àudio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca per canviar o compartir l\'àudio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Admet compartició d\'àudio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets de la pantalla de bloqueig"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per obrir una aplicació utilitzant un widget, necessitaràs verificar la teva identitat. També has de tenir en compte que qualsevol persona pot veure els widgets, fins i tot quan la tauleta està bloquejada. És possible que alguns widgets no estiguin pensats per a la pantalla de bloqueig i que no sigui segur afegir-los-hi."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entesos"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
@@ -715,7 +720,7 @@
<string name="volume_panel_hint_muted" msgid="1124844870181285320">"silenciat"</string>
<string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibra"</string>
<string name="media_output_label_title" msgid="872824698593182505">"S\'està reproduint <xliff:g id="LABEL">%s</xliff:g> a"</string>
- <string name="media_output_title_without_playing" msgid="3825663683169305013">"Es reproduirà a"</string>
+ <string name="media_output_title_without_playing" msgid="3825663683169305013">"L\'àudio es reproduirà a"</string>
<string name="media_output_title_ongoing_call" msgid="208426888064112006">"Trucant des de"</string>
<string name="system_ui_tuner" msgid="1471348823289954729">"Personalitzador d\'interfície d\'usuari"</string>
<string name="status_bar" msgid="4357390266055077437">"Barra d\'estat"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Es mostra a la part superior de les notificacions de les converses i com a foto de perfil a la pantalla de bloqueig, apareix com una bombolla, interromp el mode No molestis"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritat"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admet les funcions de converses"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Proporciona suggeriments sobre el paquet"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Aquestes notificacions no es poden modificar."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Les notificacions de trucades no es poden modificar."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Aquest grup de notificacions no es pot configurar aquí"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Canvia a l\'aplicació de l\'esquerra o de dalt amb la pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durant el mode de pantalla dividida: substitueix una app per una altra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mou la finestra activa entre pantalles"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mou la finestra cap a l\'esquerra"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mou la finestra cap a la dreta"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximitza la finestra"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimitza la finestra"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Canvia a l\'idioma següent"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Caniva a l\'idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilitza menys de <xliff:g id="LENGTH">%1$d</xliff:g> caràcters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilació"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"El número de compilació s\'ha copiat al porta-retalls."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar al porta-retalls."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversa oberta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toca una conversa per afegir-la a la teva pantalla d\'inici"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Per a una resolució més alta, gira el telèfon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"La pantalla frontal està activada"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegat"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegat"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controls del sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplicacions del sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasca"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplicacions recents"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Dreceres d\'aplicacions"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilitat"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tecles de drecera"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalitza les tecles de drecera"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Prem la tecla per assignar la drecera"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vols suprimir la drecera?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prem la tecla per assignar la drecera"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Aquesta acció suprimirà la drecera personalitzada permanentment."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Dreceres de cerca"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hi ha cap resultat de la cerca"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Replega la icona"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icona de la tecla d\'acció o Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icona del signe més"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalitza"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fet"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Desplega la icona"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"més"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra inclinada"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ansa per arrossegar"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuració del teclat"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Configura la drecera"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Suprimeix"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel·la"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Prem una tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinació de tecles ja s\'està utilitzant. Prova-ho amb una altra tecla."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinació de tecles ja s\'està utilitzant. Prova-ho amb una altra tecla."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No es pot configurar la drecera."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega amb el teclat"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprèn les tecles de drecera"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega amb el ratolí tàctil"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 1bbef7bce160..9d5a97a34bde 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Používat Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Sdílení zvuku"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Klepnutím přepnete nebo nasdílíte zvuk"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podporuje sdílení zvuku"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgety na obrazovce uzamčení"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"K otevření aplikace pomocí widgetu budete muset ověřit svou totožnost. Také mějte na paměti, že widgety uvidí kdokoli, i když tablet bude uzamčen. Některé widgety nemusí být pro obrazovku uzamčení určeny a nemusí být bezpečné je na ni přidat."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Rozumím"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgety"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Zobrazuje se v horní části sekce konverzací a na obrazovce uzamčení se objevuje jako profilová fotka, má podobu bubliny a deaktivuje režim Nerušit"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritní"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> funkce konverzace nepodporuje"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Zpětná vazba k balíčku"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Tato oznámení nelze upravit."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Upozornění na hovor nelze upravit."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Tuto skupinu oznámení tady nelze nakonfigurovat"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Přepnout na aplikaci vlevo nebo nahoře v režimu rozdělené obrazovky"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"V režimu rozdělené obrazovky: nahradit jednu aplikaci druhou"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Přesunout aktivní okno mezi obrazovkami"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Přesunout okno doleva"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Přesunout okno doprava"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximalizovat okno"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimalizovat okno"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vstup"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Přepnout na další jazyk"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Přepnout na předchozí jazyk"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Použijte méně než <xliff:g id="LENGTH">%1$d</xliff:g> znaků"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo sestavení"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Číslo sestavení bylo zkopírováno do schránky."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"zkopírovat do schránky"</string>
<string name="basic_status" msgid="2315371112182658176">"Otevřít konverzaci"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgety konverzací"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Klepnutím na konverzaci ji přidáte na plochu"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Otočte telefon, abyste dosáhli vyššího rozlišení"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Přední obrazovka je zapnutá"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"složené"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1382,7 +1390,7 @@
<string name="assistant_attention_content_description" msgid="4166330881435263596">"Je zjištěna přítomnost uživatele"</string>
<string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
<string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
- <string name="dismissible_keyguard_swipe" msgid="8377597870094949432">"Pokračujte přejetím"</string>
+ <string name="dismissible_keyguard_swipe" msgid="8377597870094949432">"Pokračovat přejetím nahoru"</string>
<string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
<string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnitřní displej bude zrcadlen. Přední displej bude vypnutý."</string>
<string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Ovládací prvky systému"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systémové aplikace"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Poslední aplikace"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Rozdělená obrazovka"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vstup"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Zkratky aplikací"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Přístupnost"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové zkratky"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Přizpůsobení klávesových zkratek"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nastavte zkratku stisknutím klávesy"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Odstrabit zkratku?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nastavte zkratku stisknutím klávesy"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Vlastní zkratka se trvale smaže."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhledat zkratky"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žádné výsledky hledání"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sbalení"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona klávesy Akce nebo Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona Plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Přizpůsobit"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hotovo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalení"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"nebo"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"lomítko"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Úchyt pro přetažení"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavení klávesnice"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastavit zkratku"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstranit"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Zrušit"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Stiskněte klávesu"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinace kláves se už používá. Použijte jinou klávesu."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinace kláves se už používá. Použijte jinou klávesu."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Zkratku není možné nastavit."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigujte pomocí klávesnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte se klávesové zkratky"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigujte pomocí touchpadu"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index b2e17a929900..53f34620c267 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tryk for at skifte eller dele lyd"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Understøtter lyddeling"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets på låseskærmen"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Hvis du vil åbne en app ved hjælp af en widget, skal du verificere din identitet. Husk også, at alle kan se dem, også når din tablet er låst. Nogle widgets er muligvis ikke beregnet til låseskærmen, og det kan være usikkert at tilføje dem her."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Vises øverst i samtalenotifikationer og som et profilbillede på låseskærmen. Vises som en boble, der afbryder Forstyr ikke"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> understøtter ikke samtalefunktioner"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Giv feedback om pakker"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Disse notifikationer kan ikke redigeres."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Opkaldsnotifikationer kan ikke redigeres."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Du kan ikke konfigurere denne gruppe notifikationer her"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skift til en app til venstre eller ovenfor, når du bruger opdelt skærm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ved opdelt skærm: Udskift én app med en anden"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flyt det aktive vindue fra skærm til skærm"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Flyt vinduet til venstre"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Flyt vinduet til højre"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimér vinduet"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimer vinduet"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Skift til næste sprog"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skift til forrige sprog"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Angiv færre end <xliff:g id="LENGTH">%1$d</xliff:g> tegn"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Buildnummeret blev kopieret til udklipsholderen."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiér til udklipsholderen."</string>
<string name="basic_status" msgid="2315371112182658176">"Åben samtale"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Samtalewidgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tryk på en samtale for at føje den til din startskærm"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vend telefonen for at få højere opløsning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontskærmen er aktiveret"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"foldet"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"foldet ud"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Systemstyring"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systemapps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Seneste apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Opdelt skærm"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Appgenveje"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hjælpefunktioner"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tastaturgenveje"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tilpas tastaturgenveje"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tryk på en tast for at tildele genvejen"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Skal genvejen fjernes?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tryk på en tast for at tildele genvej"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Denne handling sletter din tilpassede genvej permanent."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Genveje til søgning"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Der er ingen søgeresultater"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon for Skjul"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikon for handlingstast eller metatast"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plusikon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Tilpas"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Udfør"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon for Udvid"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"skråstreg"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtag"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastaturindstillinger"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Konfigurer genvej"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjern"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuller"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tryk på en tast"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tastekombinationen er allerede i brug. Prøv en anden tast."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinationen er allerede i brug. Prøv en anden tast."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Genvejen kan ikke konfigureres."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger ved hjælp af dit tastatur"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Se tastaturgenveje"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger ved hjælp af din touchplade"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 5f7128e804d0..e40445af0dbe 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audiofreigabe"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Zum Wechseln oder Teilen des Audiostreams tippen"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Unterstützt Audiofreigabe"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Sperrbildschirm-Widgets"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Wenn du eine App mit einem Widget öffnen möchtest, musst du deine Identität bestätigen. Beachte auch, dass jeder die Widgets sehen kann, auch wenn dein Tablet gesperrt ist. Einige Widgets sind möglicherweise nicht für den Sperrbildschirm vorgesehen, sodass es unsicher sein kann, sie hier hinzuzufügen."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wird oben im Bereich „Unterhaltungen“ sowie als Profilbild auf dem Sperrbildschirm angezeigt, erscheint als Bubble, unterbricht „Bitte nicht stören“"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priorität"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> unterstützt keine Funktionen für Unterhaltungen"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Feedback zu gebündelten Nachrichten geben"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Diese Benachrichtigungen können nicht geändert werden."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Anrufbenachrichtigungen können nicht geändert werden."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Die Benachrichtigungsgruppe kann hier nicht konfiguriert werden"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Im Splitscreen-Modus zu einer App links oder oben wechseln"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Im Splitscreen: eine App durch eine andere ersetzen"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktives Fenster auf anderes Display verschieben"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Fenster nach links verschieben"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Fenster nach rechts verschieben"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Fenster maximieren"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Fenster minimieren"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Eingabe"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Zur nächsten Sprache wechseln"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Zur vorherigen Sprache wechseln"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Maximal <xliff:g id="LENGTH">%1$d</xliff:g> Zeichen möglich"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-Nummer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Build-Nummer in Zwischenablage kopiert."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"In die Zwischenablage kopieren."</string>
<string name="basic_status" msgid="2315371112182658176">"Offene Unterhaltung"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Unterhaltungs-Widgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tippe auf eine Unterhaltung, um sie deinem Startbildschirm hinzuzufügen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Für höhere Auflösung Smartphone umdrehen"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontdisplay aktiviert"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zugeklappt"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aufgeklappt"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"System­steuerelemente"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"System-Apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Zuletzt verwendete Apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Splitscreen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Eingabe"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App-Verknüp­fungen"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Bedienungshilfen"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tastenkürzel"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tastenkombinationen anpassen"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Drücke eine Taste, um eine Tastenkombination festzulegen"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tastenkombination entfernen?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Drücke eine Taste, um eine Tastenkombination festzulegen"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dadurch wird die benutzerdefinierte Tastenkombination endgültig gelöscht."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tastenkürzel suchen"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Keine Suchergebnisse"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Symbol „Minimieren“"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Symbol für Aktions- oder Meta-Taste"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plussymbol"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Anpassen"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fertig"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Symbol „Maximieren“"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oder"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"Schrägstrich"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ziehpunkt"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastatureinstellungen"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tastenkombination festlegen"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Entfernen"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Abbrechen"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Taste drücken"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Diese Tastenkombination wird bereits verwendet. Versuche es mit einer anderen Taste."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Diese Tastenkombination wird bereits verwendet. Versuche es mit einer anderen Taste."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Die Tastenkombination kann nicht festgelegt werden."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigation mit der Tastatur"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informationen zu Tastenkombinationen"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigation mit dem Touchpad"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index fd8e37a34030..43fa01a1e653 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Χρήση Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Συνδέθηκε"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Κοινή χρήση ήχου"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Πατήστε για εναλλαγή ή κοινή χρήση ήχου"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Υποστηρίζει κοινή χρήση ήχου"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Αποθηκεύτηκε"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string>
@@ -450,7 +450,7 @@
<string name="zen_modes_dialog_done" msgid="6654130880256438950">"Τέλος"</string>
<string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ρυθμίσεις"</string>
<string name="zen_mode_on" msgid="9085304934016242591">"Ενεργό"</string>
- <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ενεργή • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
+ <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ενεργό • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="zen_mode_off" msgid="1736604456618147306">"Ανενεργό"</string>
<string name="zen_mode_set_up" msgid="8231201163894922821">"Δεν έχει οριστεί"</string>
<string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Διαχείριση στις ρυθμίσεις"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Γραφικά στοιχεία οθόνης κλειδώματος"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Για να ανοίξετε μια εφαρμογή χρησιμοποιώντας ένα γραφικό στοιχείο, θα πρέπει να επαληθεύσετε την ταυτότητά σας. Επίσης, λάβετε υπόψη ότι η προβολή τους είναι δυνατή από οποιονδήποτε, ακόμα και όταν το tablet σας είναι κλειδωμένο. Ορισμένα γραφικά στοιχεία μπορεί να μην προορίζονται για την οθόνη κλειδώματος και η προσθήκη τους εδώ ενδέχεται να μην είναι ασφαλής."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Το κατάλαβα"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Εμφανίζεται στην κορυφή των ειδοποιήσεων συζήτησης και ως φωτογραφία προφίλ στην οθόνη κλειδώματος, εμφανίζεται ως συννεφάκι, διακόπτει τη λειτουργία Μην ενοχλείτε"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Προτεραιότητα"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν υποστηρίζει τις λειτουργίες συζήτησης"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Παροχή σχολίων για πακέτο"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Δεν είναι δυνατή η τροποποίηση αυτών των ειδοποιήσεων"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Δεν είναι δυνατή η τροποποίηση των ειδοποιήσεων κλήσεων."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Δεν είναι δυνατή η διαμόρφωση αυτής της ομάδας ειδοποιήσεων εδώ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Εναλλαγή σε εφαρμογή αριστερά ή επάνω κατά τη χρήση διαχωρισμού οθόνης"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Κατά τον διαχωρισμό οθόνης: αντικατάσταση μιας εφαρμογής με άλλη"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Μετακίνηση ενεργού παραθύρου μεταξύ οθονών"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Μετακίνηση παραθύρου αριστερά"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Μετακίνηση παραθύρου δεξιά"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Μεγιστοποίηση παραθύρου"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Ελαχιστοποίηση παραθύρου"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Είσοδος"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Εναλλαγή στην επόμενη γλώσσα"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Εναλλαγή στην προηγούμενη γλώσσα"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Χρησιμοποιήστε λιγότερους από <xliff:g id="LENGTH">%1$d</xliff:g> χαρακτήρες"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Αριθμός έκδοσης"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Ο αριθμός έκδοσης αντιγράφηκε στο πρόχειρο."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"αντιγραφή στο πρόχειρο."</string>
<string name="basic_status" msgid="2315371112182658176">"Άνοιγμα συνομιλίας"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Γραφικά στοιχεία συνομιλίας"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Πατήστε μια συνομιλία για να την προσθέσετε στην αρχική οθόνη"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Για υψηλότερη ανάλυση, αναστρέψτε το τηλέφωνο"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Η μπροστινή οθόνη ενεργοποιήθηκε"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"διπλωμένη"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ξεδιπλωμένη"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Στοιχεία ελέγχου συστήματος"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Εφαρμογές συστήματος"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Πολυδιεργασία"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Πρόσφατες εφαρμογές"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Διαχωρισμός οθόνης"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Εισαγωγή"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Συντομεύσεις εφαρμογών"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Προσβασιμότητα"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Συντομεύσεις πληκτρολογίου"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Προσαρμογή συντομεύσεων πληκτρολογίου"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Πατήστε το πλήκτρο για εκχώρηση της συντόμευσης"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Κατάργηση συντόμευσης;"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Πατήστε το πλήκτρο για ανάθεση της συντόμευσης"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Με αυτή την ενέργεια, η προσαρμοσμένη συντόμευση θα διαγραφεί οριστικά."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Συντομεύσεις αναζήτησης"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Κανένα αποτέλεσμα αναζήτησης"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Εικονίδιο σύμπτυξης"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Εικονίδιο πλήκτρου ενέργειας ή Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Εικονίδιο συν"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Προσαρμογή"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Τέλος"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Εικονίδιο ανάπτυξης"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ή"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"συν"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"κάθετος"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Λαβή μεταφοράς"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ρυθμίσεις πληκτρολογίου"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ορισμός συντόμευσης"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Κατάργηση"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Ακύρωση"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Πατήστε ένα πλήκτρο"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε άλλο πλήκτρο."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε άλλο πλήκτρο."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Δεν είναι δυνατή η ρύθμιση της συντόμευσης."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Πλοήγηση με το πληκτρολόγιο"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Μάθετε συντομεύσεις πληκτρολογίου"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Πλοήγηση με την επιφάνεια αφής"</string>
diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml
index ec2a930dcb75..5ce4b8c820fb 100644
--- a/packages/SystemUI/res/values-el/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml
@@ -58,7 +58,7 @@
</string-array>
<string-array name="tile_states_flashlight">
<item msgid="3465257127433353857">"Μη διαθέσιμο"</item>
- <item msgid="5044688398303285224">"Ανενεργό"</item>
+ <item msgid="5044688398303285224">"Ανενεργός"</item>
<item msgid="8527389108867454098">"Ενεργό"</item>
</string-array>
<string-array name="tile_states_rotation">
@@ -73,7 +73,7 @@
</string-array>
<string-array name="tile_states_airplane">
<item msgid="1985366811411407764">"Μη διαθέσιμο"</item>
- <item msgid="4801037224991420996">"Ανενεργό"</item>
+ <item msgid="4801037224991420996">"Ανενεργή"</item>
<item msgid="1982293347302546665">"Ενεργό"</item>
</string-array>
<string-array name="tile_states_location">
@@ -113,7 +113,7 @@
</string-array>
<string-array name="tile_states_cast">
<item msgid="6032026038702435350">"Μη διαθέσιμο"</item>
- <item msgid="1488620600954313499">"Ανενεργό"</item>
+ <item msgid="1488620600954313499">"Ανενεργή"</item>
<item msgid="588467578853244035">"Ενεργό"</item>
</string-array>
<string-array name="tile_states_night">
@@ -152,7 +152,7 @@
<item msgid="8998632451221157987">"Ενεργό"</item>
</string-array>
<string-array name="tile_states_controls">
- <item msgid="8199009425335668294">"Μη διαθέσιμο"</item>
+ <item msgid="8199009425335668294">"Μη διαθέσιμα"</item>
<item msgid="4544919905196727508">"Ανενεργό"</item>
<item msgid="3422023746567004609">"Ενεργό"</item>
</string-array>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4280ff2ad994..d32c95acc5e6 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Move window to the left"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Move window to the right"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximise window"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimise window"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"System controls"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"System apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Recent apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Split screen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Action or Meta key icon"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus icon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Customise"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 3b009a1f1d14..8a7420d9dd96 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio Sharing"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,9 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you’ll need to verify it’s you. Also, keep in mind that anyone can view them, even when your tablet’s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="599170482297578735">"To add the \"Widgets\" shortcut, make sure \"Show widgets on lock screen\" is enabled in settings."</string>
+ <string name="glanceable_hub_lockscreen_affordance_action_button_label" msgid="7636151133344609375">"Settings"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -780,6 +783,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide Bundle Feedback"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +876,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to app on left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: replace an app from one to another"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Move window to the left"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Move window to the right"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximize window"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimize window"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1228,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your Home screen"</string>
@@ -1409,7 +1416,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"System controls"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"System apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Recent apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Split screen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string>
@@ -1417,22 +1423,34 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customize keyboard shortcuts"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+ <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Reset back to default?"</string>
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
+ <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"This will delete all your custom shortcuts permanently."</string>
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Action or Meta key icon"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus icon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Customize"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard Settings"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
+ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Yes, reset"</string>
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4280ff2ad994..d32c95acc5e6 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Move window to the left"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Move window to the right"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximise window"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimise window"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"System controls"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"System apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Recent apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Split screen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Action or Meta key icon"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus icon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Customise"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4280ff2ad994..d32c95acc5e6 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Move window to the left"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Move window to the right"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximise window"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimise window"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"System controls"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"System apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Recent apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Split screen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Action or Meta key icon"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus icon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Customise"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index ad403b1658ee..711f771842e2 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Uso compartido de audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Presiona para cambiar o compartir el audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Admite el uso compartido de audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -450,7 +450,7 @@
<string name="zen_modes_dialog_done" msgid="6654130880256438950">"Listo"</string>
<string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Configuración"</string>
<string name="zen_mode_on" msgid="9085304934016242591">"Activado"</string>
- <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Sí • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
+ <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Activado • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="zen_mode_off" msgid="1736604456618147306">"Desactivado"</string>
<string name="zen_mode_set_up" msgid="8231201163894922821">"Sin establecer"</string>
<string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Administrar en configuración"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets en la pantalla de bloqueo"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir una app usando un widget, debes verificar tu identidad. Además, ten en cuenta que cualquier persona podrá verlo, incluso cuando la tablet esté bloqueada. Es posible que algunos widgets no se hayan diseñados para la pantalla de bloqueo y podría ser peligroso agregarlos allí."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparece en forma de burbuja y como foto de perfil en la parte superior de las notificaciones de conversación, en la pantalla de bloqueo, y detiene el modo No interrumpir"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritaria"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite funciones de conversación"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Proporcionar comentarios sobre el conjunto"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"No se pueden modificar estas notificaciones."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"No se pueden modificar las notificaciones de llamada."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"No se puede configurar aquí este grupo de notificaciones"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ubicar la app a la izquierda o arriba cuando usas la pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durante pantalla dividida: Reemplaza una app con otra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover la ventana activa de una pantalla a otra"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover la ventana hacia la izquierda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover la ventana hacia la derecha"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar ventana"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar ventana"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar al próximo idioma"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar al idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Se copió el número de compilación en el portapapeles."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar en el portapapeles."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversación abierta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversación"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Presiona una conversación para agregarla a tu pantalla principal"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para obtener una resolución más alta, gira el teléfono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Pantalla frontal activada"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controles del sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apps del sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Tareas múltiples"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Apps recientes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Accesos directos a aplicaciones"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personaliza las combinaciones de teclas"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Presiona la tecla para asignar el acceso directo"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Quieres quitar el acceso directo?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Presiona la tecla para asignar el acceso directo"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta acción borrará tu acceso directo personalizado de forma permanente."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar combinaciones de teclas"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"La búsqueda no arrojó resultados"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícono de contraer"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ícono tecla meta o de acción"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ícono de signo más"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Listo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícono de expandir"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"más"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra diagonal"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuración del teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Establecer combinación de teclas"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Quitar"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Presiona una tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinación de teclas ya está en uso. Prueba con otra."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinación de teclas ya está en uso. Prueba con otra."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No se puede establecer la combinación de teclas."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega con el teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega con el panel táctil"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 50de6d2e452a..4a94cdfd0852 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartir audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca para cambiar o compartir el audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Permite compartir audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets para la pantalla de bloqueo"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir una aplicación usando un widget, deberás verificar que eres tú. Además, ten en cuenta que cualquier persona podrá verlos, incluso aunque tu tablet esté bloqueada. Es posible que algunos widgets no estén pensados para la pantalla de bloqueo y no sea seguro añadirlos aquí."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Se muestra encima de las notificaciones de conversaciones y como imagen de perfil en la pantalla de bloqueo, aparece como burbuja e interrumpe el modo No molestar"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridad"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite funciones de conversación"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar comentarios sobre el paquete"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Estas notificaciones no se pueden modificar."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Las notificaciones de llamada no se pueden modificar."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Este grupo de notificaciones no se puede configurar aquí"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar a la app de la izquierda o de arriba en pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Con pantalla dividida: reemplazar una aplicación por otra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover ventana activa de una pantalla a otra"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover ventana a la izquierda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover ventana a la derecha"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar ventana"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar ventana"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar a siguiente idioma"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar a idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Número de compilación copiado en el portapapeles."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar en el portapapeles."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversación abierta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversación"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toca una conversación para añadirla a la pantalla de inicio"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para una mayor resolución, gira el teléfono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Pantalla frontal encendida"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controles del sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplicaciones del sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitarea"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplicaciones recientes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Accesos directos a aplicaciones"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar las combinaciones de teclas"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pulsa una tecla para asignar una combinación de teclas"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Eliminar combinación de teclas?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pulsa una tecla para asignar una combinación de teclas"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Se eliminará tu combinación de teclas personalizada de forma permanente."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atajos de búsqueda"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hay resultados de búsqueda"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icono de contraer"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icono de la tecla de acción o de la tecla Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icono de más"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hecho"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icono de desplegar"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"más"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra inclinada"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ajustes del teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Establecer combinación de teclas"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eliminar"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pulsa una tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinación de teclas ya se está usando. Prueba con otra tecla."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinación de teclas ya se está usando. Prueba con otra tecla."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No se puede configurar la combinación de teclas."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Desplázate con el teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Desplázate con el panel táctil"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 9c0d56dad8c3..4027bbdd9ca1 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Heli jagamine"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Puudutage helile lülitumiseks või selle jagamiseks"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Toetab heli jagamist"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lukustuskuva vidinad"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Rakenduse avamiseks vidina abil peate kinnitama, et see olete teie. Samuti pidage meeles, et kõik saavad vidinaid vaadata, isegi kui teie tahvelarvuti on lukus. Mõni vidin ei pruugi olla ette nähtud teie lukustuskuva jaoks ja seda pole turvaline siia lisada."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Selge"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidinad"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Kuvatakse mullina vestluste märguannete ülaosas ja profiilipildina lukustuskuval ning katkestab režiimi Mitte segada"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioriteetne"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei toeta vestlusfunktsioone"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Kogumi kohta tagasiside andmine"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Neid märguandeid ei saa muuta."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Kõnemärguandeid ei saa muuta."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Seda märguannete rühma ei saa siin seadistada"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vasakule või ülemisele rakendusele lülitamine jagatud ekraani ajal"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ekraanikuva jagamise ajal: ühe rakenduse asendamine teisega"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktiivse akna teisaldamine ekraanide vahel"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Akna vasakule liigutamine"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Akna paremale liigutamine"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Akna maksimeerimine"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Akna minimeerimine"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Sisend"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Järgmisele keelele lülitamine"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Eelmisele keelele lülitamine"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Kasutage vähem kui <xliff:g id="LENGTH">%1$d</xliff:g> tähemärki"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Järgunumber"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Järgunumber kopeeriti lõikelauale."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"lõikelauale kopeerimine."</string>
<string name="basic_status" msgid="2315371112182658176">"Avage vestlus"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Vestlusvidinad"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Puudutage vestlust, et lisada see oma avakuvale"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Suurema eraldusvõime saavutamiseks pöörake telefon ümber"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Esiekraan on sisse lülitatud"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kokku volditud"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"lahti volditud"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Süsteemi juhtelemendid"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Süsteemirakendused"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitegumtöö"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Hiljutised rakendused"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Jagatud ekraanikuva"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Sisend"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Rakenduse otseteed"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Juurdepääsetavus"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatuuri otseteed"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klaviatuuri otseteede kohandamine"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Otsetee lisamiseks vajutage klahvi"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kas soovite otsetee eemaldada?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Otsetee lisamiseks vajutage klahvi"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"See kustutab teie kohandatud otsetee jäädavalt."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Otsige otseteid"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Otsingutulemused puuduvad"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ahendamisikoon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Toiming või metaklahv"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pluss-ikoon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Kohandamine"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Valmis"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laiendamisikoon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"või"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"pluss"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"kaldkriips"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Lohistamispide"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatuuri seaded"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Määrake otsetee"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eemalda"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Tühista"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Vajutage klahvi"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Klahvikombinatsioon juba kasutusel. Proovige mõnda muud klahvi."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Klahvikombinatsioon on juba kasutusel. Proovige mõnda muud klahvi."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Otseteed ei saa seadistada."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeerige klaviatuuri abil"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Õppige klaviatuuri otseteid"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeerige puuteplaadi abil"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index a971d1c25a16..a0ba1b78a081 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audioa partekatzea"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Audioa aldatu edo partekatzeko, sakatu hau"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audioa partekatzeko eginbidea onartzen du"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
@@ -384,8 +384,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Egunsentira arte"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Desaktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Aktibatuta lo egiteko garaian"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Lo egiteko garaia amaitzen den arte"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Aktibatuta lotara joateko garaian"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Lotara joateko garaia amaitzen den arte"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFCa"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Desgaituta dago NFC"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Gaituta dago NFC"</string>
@@ -455,7 +455,7 @@
<string name="zen_mode_set_up" msgid="8231201163894922821">"Ezarri gabe"</string>
<string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Kudeatu ezarpenetan"</string>
<string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{Ez dago modurik aktibo}=1{\"{mode}\" aktibo dago}other{# modu aktibo daude}}"</string>
- <string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta abisuen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
+ <string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta gogorarazpenen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
<string name="zen_alarms_introduction" msgid="3987266042682300470">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak joko ditu. Hala ere, zuk erreproduzitutako guztia entzun ahal izango duzu, besteak beste, musika, bideoak eta jokoak."</string>
<string name="zen_priority_customize_button" msgid="4119213187257195047">"Pertsonalizatu"</string>
<string name="zen_silence_introduction_voice" msgid="853573681302712348">"Soinu eta dardara GUZTIAK blokeatuko dira, besteak beste, alarmak, musika, bideoak eta jokoak. Telefono-deiak egiteko aukera izaten jarraituko duzu."</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Pantaila blokeatuko widgetak"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Aplikazio bat widget baten bidez irekitzeko, zeu zarela egiaztatu beharko duzu. Gainera, kontuan izan edonork ikusi ahalko dituela halako widgetak, tableta blokeatuta badago ere. Baliteke widget batzuk pantaila blokeaturako egokiak ez izatea, eta agian ez da segurua haiek bertan gehitzea."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ados"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetak"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Elkarrizketen jakinarazpenen goialdean eta profileko argazki gisa agertzen da pantaila blokeatuan, burbuila batean, eta ez molestatzeko modua eteten du"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Lehentasuna"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak ez ditu onartzen elkarrizketetarako eginbideak"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Bidali sortari buruzko oharrak"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Jakinarazpen horiek ezin dira aldatu."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Deien jakinarazpenak ezin dira aldatu."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Jakinarazpen talde hau ezin da konfiguratu hemen"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Aldatu ezkerreko edo goiko aplikaziora pantaila zatitua erabiltzean"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Pantaila zatituan zaudela, ordeztu aplikazio bat beste batekin"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Eraman leiho aktiboa pantaila batetik bestera"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Eraman leihoa ezkerrera"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Eraman leihoa eskuinera"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizatu leihoa"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizatu leihoa"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Sarrera"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Aldatu hurrengo hizkuntzara"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Aldatu aurreko hizkuntzara"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Erabili <xliff:g id="LENGTH">%1$d</xliff:g> karaktere baino gutxiago"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Konpilazio-zenbakia"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Kopiatu da konpilazio-zenbakia arbelean."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiatu arbelean."</string>
<string name="basic_status" msgid="2315371112182658176">"Elkarrizketa irekia"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Elkarrizketa-widgetak"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat orri nagusian gehitzeko"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Irauli telefonoa bereizmen handiago a lortzeko"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Aurreko pantaila piztuta dago"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"tolestuta"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tolestu gabe"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistema kontrolatzeko aukerak"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistemaren aplikazioak"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Zeregin bat baino gehiago aldi berean exekutatzea"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Azkenaldiko aplikazioak"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Pantaila zatitzea"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Sarrera"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Aplikazioetarako lasterbideak"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erabilerraztasuna"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Lasterbideak"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pertsonalizatu lasterbideak"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Sakatu tekla lasterbidea esleitzeko"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Lasterbidea kendu nahi duzu?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Sakatu tekla lasterbidea esleitzeko"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Betiko ezabatuko da lasterbide pertsonalizatua."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bilatu lasterbideak"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ez dago bilaketa-emaitzarik"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tolesteko ikonoa"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ekintzaren edo Meta teklaren ikonoa"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus-ikonoa"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Pertsonalizatu"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Eginda"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Zabaltzeko ikonoa"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"edo"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"gehi"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Arrastatzeko kontrol-puntua"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Teklatuaren ezarpenak"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ezarri lasterbidea"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Kendu"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Utzi"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Sakatu tekla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste tekla bat."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste tekla bat."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ezin da ezarri lasterbidea."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nabigatu teklatua erabilita"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ikasi lasterbideak"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nabigatu ukipen-panela erabilita"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index bf7ccf0d8079..7bd01bc1a322 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"استفاده از بلوتوث"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متصل"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"اشتراک صدا"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"برای فعال کردن و هم‌رسانی صدا، تک‌ضرب بزنید"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"از اشتراک صدا پشتیبانی می‌کند"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ذخیره‌شده"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ابزاره‌های صفحه قفل"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"برای باز کردن برنامه بااستفاده از ابزاره، باید هویت خودتان را به‌تأیید برسانید. همچنین، به‌خاطر داشته باشید که همه می‌توانند آن‌ها را مشاهده کنند، حتی وقتی رایانه لوحی‌تان قفل است. برخی‌از ابزاره‌ها ممکن است برای صفحه قفل درنظر گرفته نشده باشند و ممکن است اضافه کردن آن‌ها در اینجا ناامن باشد."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"متوجه‌ام"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ابزاره‌ها"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایین‌پر"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامه‌ها و داده‌های این جلسه حذف خواهد شد."</string>
@@ -695,7 +700,7 @@
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. برای صامت کردن تک‌ضرب بزنید. ممکن است سرویس‌های دسترس‌پذیری صامت شود."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. برای تنظیم روی لرزش، تک‌ضرب بزنید."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. برای صامت کردن تک‌ضرب بزنید."</string>
- <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل صدای محیط"</string>
+ <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل نوفه زمینه"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"صدای فضایی"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"خاموش"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"ثابت"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"در بالای اعلان‌های مکالمه و به‌صورت عکس نمایه در صفحه قفل نشان داده می‌شود، به‌صورت حبابک ظاهر می‌شود، در حالت «مزاحم نشوید» وقفه ایجاد می‌کند"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"اولویت"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> از ویژگی‌های مکالمه پشتیبانی نمی‌کند"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ارائه بازخورد دسته‌ای"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"این اعلان‌ها قابل اصلاح نیستند."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"این اعلان‌ها قابل‌اصلاح نیستند."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"نمی‌توانید این گروه اعلان‌ها را در اینجا پیکربندی کنید"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"رفتن به برنامه سمت چپ یا بالا درحین استفاده از صفحهٔ دونیمه"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"درحین صفحهٔ دونیمه: برنامه‌ای را با دیگری جابه‌جا می‌کند"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"جابه‌جا کردن پنجره فعال بین نمایشگرها"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"بردن پنجره به چپ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"بردن پنجره به راست"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"به‌حداکثر رساندن اندازه پنجره"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"به‌حداقل رساندن اندازه پنجره"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ورودی"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"رفتن به زبان بعدی"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"رفتن به زبان قبلی"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"از کمتر از <xliff:g id="LENGTH">%1$d</xliff:g> نویسه استفاده کنید"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"شماره ساخت"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"شماره ساخت در بریده‌دان کپی شد."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"کپی کردن در بریده‌دان."</string>
<string name="basic_status" msgid="2315371112182658176">"باز کردن مکالمه"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ابزارک‌های مکالمه"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"روی مکالمه‌ای تک‌ضرب بزنید تا به «صفحه اصلی» اضافه شود"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را بچرخانید"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"صفحه‌نمایش جلو روشن شد"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"تاشده"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"تانشده"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"کنترل‌های سیستم"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"برنامه‌های سیستم"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"چندوظیفگی"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"برنامه‌های اخیر"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"صفحهٔ دونیمه"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ورودی"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"میان‌برهای برنامه"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"دسترس‌پذیری"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"میان‌برهای صفحه‌کلید"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"سفارشی‌سازی کردن میان‌برهای صفحه‌کلید"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"برای اختصاص دادن میان‌بر، کلید را فشار دهید"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"میان‌بر حذف شود؟"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"برای اختصاص دادن میان‌بر، کلید را فشار دهید"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"با این کار، میان‌بر سفارشی شما برای همیشه حذف می‌شود."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"جستجوی میان‌برها"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"نتیجه‌ای برای جستجو پیدا نشد"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"نماد جمع کردن"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"نماد کلید کنش یا متا"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"نماد جمع"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"سفارشی‌سازی کردن"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"تمام"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"نماد ازهم بازکردن"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"و"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"کج‌خط روبه جلو"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"دستگیره کشاندن"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"تنظیمات صفحه‌کلید"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"تنظیم میان‌بر"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"حذف"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"لغو"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"کلید را فشار دهید"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ترکیب کلید ازقبل درحال استفاده است. کلید دیگری را امتحان کنید."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ترکیب کلید ازقبل درحال استفاده است. کلید دیگری را امتحان کنید."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"میان‌بر تنظیم نشد."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"پیمایش کردن بااستفاده از صفحه‌کلید"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"آشنایی با میان‌برهای صفحه‌کلید"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"پیمایش کردن بااستفاده از صفحه لمسی"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 89ed95f0115f..e6174515c8d4 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audionjako"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Vaihda tai jaa audiota napauttamalla"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Tukee audionjakoa"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lukitusnäytön widgetit"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Jos haluat avata sovelluksen käyttämällä widgetiä, sinun täytyy vahvistaa henkilöllisyytesi. Muista myös, että widgetit näkyvät kaikille, vaikka tabletti olisi lukittuna. Jotkin widgetit on ehkä tarkoitettu lukitusnäytölle, ja niiden lisääminen tänne ei välttämättä ole turvallista."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Selvä"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetit"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Näkyy keskusteluilmoitusten yläosassa ja profiilikuvana lukitusnäytöllä, näkyy kuplana, keskeyttää Älä häiritse ‑tilan"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Tärkeä"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei tue keskusteluominaisuuksia"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Anna palautetta paketista"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Näitä ilmoituksia ei voi muokata"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Puheluilmoituksia ei voi muokata."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Tätä ilmoitusryhmää ei voi määrittää tässä"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vaihda sovellukseen vasemmalla tai yläpuolella jaetussa näytössä"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Jaetun näytön aikana: korvaa sovellus toisella"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Siirrä aktiivinen ikkuna näytöltä toiselle"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Siirrä ikkuna vasemmalle"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Siirrä ikkuna oikealle"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Suurenna ikkuna"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Pienennä ikkuna"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Syöttötapa"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Vaihda seuraavaan kieleen"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Vaihda aiempaan kieleen"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Käytä alle <xliff:g id="LENGTH">%1$d</xliff:g> merkkiä"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Koontiversion numero"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Koontiversion numero kopioitu leikepöydälle"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopioi leikepöydälle."</string>
<string name="basic_status" msgid="2315371112182658176">"Avaa keskustelu"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Keskusteluwidgetit"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Lisää keskustelu aloitusnäytölle napauttamalla sitä"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Resoluutio on parempi, kun käännät puhelimen"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Etunäyttö päällä"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"taitettu"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"taittamaton"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Järjestelmän hallinta"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Järjestelmäsovellukset"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitaskaus"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Viimeisimmät sovellukset"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Jaettu näyttö"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Syöte"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Sovellusten pikakuvakkeet"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Saavutettavuus"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Pikanäppäimet"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pikanäppäimien muokkaaminen"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Määritä pikanäppäin painamalla näppäintä"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Poistetaanko pikanäppäin?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Määritä pikanäppäin painamalla näppäintä"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Oma pikanäppäin poistetaan pysyvästi."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pikahaut"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ei hakutuloksia"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tiivistyskuvake"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Toiminto- tai Meta-näppäinkuvake"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pluskuvake"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Muokkaa"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Valmis"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laajennuskuvake"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"tai"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"kauttaviiva"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vetokahva"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Näppäimistön asetukset"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Valitse pikanäppäin"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Poista"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Peru"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Paina näppäintä"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Näppäinyhdistelmä on jo käytössä. Kokeile toista näppäintä."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Näppäinyhdistelmä on jo käytössä. Kokeile toista näppäintä."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pikakuvaketta ei voi lisätä."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Siirry käyttämällä näppäimistöä"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Opettele pikanäppäimiä"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Siirry käyttämällä kosketuslevyä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 708d8b910306..6213dd581b57 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Touchez pour passer d\'un appareil à l\'autre ou pour partager le contenu audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Prise en charge du partage audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets de l\'écran de verrouillage"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pour ouvrir une appli à l\'aide d\'un widget, vous devrez confirmer votre identité. En outre, gardez à l\'esprit que tout le monde peut voir les widgets, même lorsque votre tablette est verrouillée. Certains widgets n\'ont peut-être pas été conçus pour votre écran de verrouillage, et il pourrait être dangereux de les ajouter ici."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applis et les données de cette session seront supprimées."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"S\'affiche dans le haut des notifications de conversation et comme photo de profil à l\'écran de verrouillage, s\'affiche comme bulle, interrompt le mode Ne pas déranger"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritaire"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne prend pas en charge les fonctionnalités de conversation"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Fournir des commentaires groupés"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ces notifications ne peuvent pas être modifiées"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Les notifications d\'appel ne peuvent pas être modifiées."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ce groupe de notifications ne peut pas être configuré ici"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passer à l\'appli à gauche ou au-dessus avec l\'Écran divisé"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En mode d\'écran divisé : remplacer une appli par une autre"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Déplacer la fenêtre active d\'un écran à l\'autre"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Déplacer la fenêtre vers la gauche"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Déplacer la fenêtre vers la droite"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Agrandir la fenêtre"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Réduire la fenêtre"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrée"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Passer à la langue suivante"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Passer à la langue précédente"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilisez moins de <xliff:g id="LENGTH">%1$d</xliff:g> caractères"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de version"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Le numéro de version a été copié dans le presse-papiers."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Copier le contenu dans le presse-papiers"</string>
<string name="basic_status" msgid="2315371112182658176">"Ouvrir la conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversation"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Touchez une conversation pour l\'ajouter à votre écran d\'accueil"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une meilleure résolution, retournez le téléphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Écran avant activé"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Commandes système"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Applis système"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitâche"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Applis récentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Écran divisé"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrée"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Raccourcis des applis"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis-clavier"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personnaliser les raccourcis-clavier"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Appuyez sur la touche pour attribuer un raccourci"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur la touche pour attribuer un raccourci"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Cela supprimera définitivement votre raccourci personnalisé."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icône de la touche Action ou Méta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icône Plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personnaliser"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Terminé"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barre oblique"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Paramètres du clavier"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Définir un raccourci"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Supprimer"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuler"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Appuyez sur la touche"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinaison de touches est déjà utilisée. Essayez une autre touche."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Le raccourci ne peut pas être défini."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide de votre clavier"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Apprenez à utiliser les raccourcis-clavier"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 41bd9106fb45..d0931a687947 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Appuyez pour activer ou partager l\'audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatible avec le partage audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets pour l\'écran de verrouillage"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pour ouvrir une appli à l\'aide d\'un widget, vous devez confirmer qu\'il s\'agit bien de vous. N\'oubliez pas non plus que tout le monde peut voir vos widgets, même lorsque votre tablette est verrouillée. Certains d\'entre eux n\'ont pas été conçus pour l\'écran de verrouillage et les ajouter à cet endroit peut s\'avérer dangereux."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"S\'affiche en haut des notifications de conversation et en tant que photo de profil sur l\'écran de verrouillage, apparaît sous forme de bulle, interrompt le mode Ne pas déranger"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritaire"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas compatible avec les fonctionnalités de conversation"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Envoyer des commentaires groupés"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Impossible de modifier ces notifications."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Impossible de modifier les notifications d\'appel."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Vous ne pouvez pas configurer ce groupe de notifications ici"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passez à l\'appli à gauche ou au-dessus avec l\'écran partagé"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En mode écran partagé : Remplacer une appli par une autre"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Déplacer la fenêtre active d\'un écran à l\'autre"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Déplacer la fenêtre vers la gauche"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Déplacer la fenêtre vers la droite"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Agrandir la fenêtre"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Réduire la fenêtre"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Saisie"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Passer à la langue suivante"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Revenir à la langue précédente"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilisez moins de <xliff:g id="LENGTH">%1$d</xliff:g> caractères"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de build"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Numéro de build copié dans le presse-papiers."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copier dans le presse-papiers."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversation ouverte"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversation"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Appuyez sur une conversation pour l\'ajouter à votre écran d\'accueil"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une résolution plus élevée, retournez le téléphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Écran avant activé"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Commandes système"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Applis système"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitâche"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Applis récentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Écran partagé"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrée"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Raccourcis d\'application"</string>
@@ -1418,28 +1425,43 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis clavier"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personnaliser les raccourcis clavier"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Appuyez sur une touche pour attribuer un raccourci"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur une touche pour attribuer un raccourci"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Votre raccourci personnalisé sera définitivement supprimé."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icône de touche d\'action ou de méta-touche"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icône Plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personnaliser"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"OK"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barre oblique"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Paramètres du clavier"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Définir un raccourci"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Supprimer"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuler"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Appuyez sur la touche"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Impossible de définir le raccourci."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide du clavier"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Apprenez à utiliser les raccourcis clavier"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Découvrez les gestes au pavé tactile"</string>
<string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Naviguer à l\'aide de votre clavier et de votre pavé tactile"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Découvrir les gestes au pavé tactile, les raccourcis clavier et plus encore"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Découvrir les gestes du pavé tactile, les raccourcis clavier et plus encore"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Retour"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Retour à l\'accueil"</string>
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Afficher les applis récentes"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 398360689afe..a02206ddc1a5 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio compartido"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca para cambiar ou compartir o audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatible con audio compartido"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da pantalla de bloqueo"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir unha aplicación mediante un widget, tes que verificar a túa identidade. Ten en conta que pode velos calquera persoa, mesmo coa tableta bloqueada. Pode ser que algúns widgets non estean pensados para a túa pantalla de bloqueo, polo que talvez non sexa seguro engadilos aquí."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Móstrase na parte superior das notificacións das conversas e como imaxe do perfil na pantalla de bloqueo, aparece como unha burbulla e interrompe o modo Non molestar"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non admite funcións de conversa"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar comentarios agrupados"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Estas notificacións non se poden modificar."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"As notificacións de chamadas non se poden modificar."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Aquí non se pode configurar este grupo de notificacións"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar á aplicación da esquerda ou de arriba coa pantalla dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"En modo de pantalla dividida: Substituír unha aplicación por outra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover ventá activa entre pantallas"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover ventá á esquerda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover ventá á dereita"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar ventá"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar ventá"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar ao seguinte idioma"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar ao idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utiliza menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Copiouse o número de compilación no portapapeis."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar no portapapeis."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toca unha conversa para engadila á pantalla de inicio"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Dálle a volta ao teléfono para gozar dunha maior resolución"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Activouse a pantalla dianteira"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dispositivo pregado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dispositivo despregado"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controis do sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplicacións do sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitarefa"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplicacións recentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atallos de aplicacións"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidade"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Atallos de teclado"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar os atallos de teclado"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Preme a tecla para asignar o atallo"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Queres quitar o atallo?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Preme a tecla para asignar o atallo"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Eliminarase de forma permanente o teu atallo personalizado."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Busca atallos"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Non hai resultados de busca"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona de contraer"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icona da tecla Meta ou de acción"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icona do signo máis"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Feito"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona de despregar"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"máis"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra oblicua"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuración do teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atallo"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Quitar"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Preme unha tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Xa se está usando esta combinación de teclas. Proba con outra."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Xa se está usando esta combinación de teclas. Proba con outra."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Non se puido definir o atallo."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega co teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende a usar os atallos de teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega co panel táctil"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 89012697cfac..8cff7ff01eca 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"બ્લૂટૂથનો ઉપયોગ કરો"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"કનેક્ટેડ છે"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ઑડિયો શેરિંગ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ઑડિયો પર સ્વિચ કરવા કે તેને શેર કરવા માટે બે વાર ટૅપ કરો"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ઑડિયો શેરિંગને સપોર્ટ કરે છે"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"સાચવેલું"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"લૉક સ્ક્રીન વિજેટ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"વિજેટનો ઉપયોગ કરીને ઍપ ખોલવા માટે, તમારે એ ચકાસણી કરવાની જરૂર રહેશે કે આ તમે જ છો. તે ઉપરાંત, ધ્યાનમાં રાખો કે તમારું ટૅબ્લેટ લૉક કરેલું હોય તો પણ કોઈપણ વ્યક્તિ તેમને જોઈ શકે છે. અમુક વિજેટ કદાચ તમારી લૉક સ્ક્રીન માટે બનાવવામાં આવ્યા ન હોઈ શકે છે અને તેમને અહીં ઉમેરવાનું અસલામત હોઈ શકે છે."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"સમજાઈ ગયું"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"વિજેટ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
@@ -672,9 +677,9 @@
<string name="screen_pinning_negative" msgid="6882816864569211666">"ના, આભાર"</string>
<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_voice_call" msgid="7468348170702375660">"કૉલ"</string>
<string name="stream_system" msgid="7663148785370565134">"સિસ્ટમ"</string>
- <string name="stream_ring" msgid="7550670036738697526">"રિંગ વગાડો"</string>
+ <string name="stream_ring" msgid="7550670036738697526">"રિંગ"</string>
<string name="stream_music" msgid="2188224742361847580">"મીડિયા"</string>
<string name="stream_alarm" msgid="16058075093011694">"અલાર્મ"</string>
<string name="stream_notification" msgid="7930294049046243939">"નોટિફિકેશન"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"વાતચીતના નોટિફિકેશન વિભાગની ટોચ પર અને લૉક કરેલી સ્ક્રીન પર પ્રોફાઇલ ફોટો તરીકે બતાવે છે, બબલ તરીકે દેખાય છે, ખલેલ પાડશો નહીં મોડમાં વિક્ષેપ ઊભો કરે છે"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"પ્રાધાન્યતા"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> વાતચીતની સુવિધાઓને સપોર્ટ આપતી નથી"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"બંડલ પ્રતિસાદ પ્રદાન કરો"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"આ નોટિફિકેશનમાં કોઈ ફેરફાર થઈ શકશે નહીં."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"કૉલના નોટિફિકેશનમાં કોઈ ફેરફાર કરી શકાતો નથી."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"નોટિફિકેશનના આ ગ્રૂપની ગોઠવણી અહીં કરી શકાશે નહીં"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"વિભાજિત સ્ક્રીનનો ઉપયોગ કરતી વખતે ડાબી બાજુની કે ઉપરની ઍપ પર સ્વિચ કરો"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"વિભાજિત સ્ક્રીન દરમિયાન: એક ઍપને બીજી ઍપમાં બદલો"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"સક્રિય વિન્ડોને ડિસ્પ્લેની વચ્ચે ખસેડો"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"વિંડોને ડાબી બાજુ ખસેડો"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"વિંડોને જમણી બાજુ ખસેડો"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"વિંડો મોટી કરો"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"વિંડો નાની કરો"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ઇનપુટ"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"આગલી ભાષા પર સ્વિચ કરો"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"પાછલી ભાષા પર સ્વિચ કરો"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> કરતાં ઓછા અક્ષરનો ઉપયોગ કરો"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"બિલ્ડ નંબર"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"બિલ્ડ નંબર ક્લિપબૉર્ડ પર કૉપિ કર્યો."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ક્લિપબોર્ડ પર કૉપિ કરો."</string>
<string name="basic_status" msgid="2315371112182658176">"વાતચીત ખોલો"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"વાતચીતના વિજેટ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"તમારી હોમ સ્ક્રીનમાં વાતચીત ઉમેરવા માટે તેના પર ટૅપ કરો"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"વધુ રિઝોલ્યુશન માટે, ફોનને ફ્લિપ કરો"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ફ્રન્ટ સ્ક્રીનની સુવિધા ચાલુ કરેલી છે"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ફોલ્ડ કરેલું"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"અનફોલ્ડ કરેલું"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"સિસ્ટમના નિયંત્રણો"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"સિસ્ટમ ઍપ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"એકથી વધુ કાર્યો કરવા"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"તાજેતરની ઍપ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"સ્ક્રીનને વિભાજિત કરો"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ઇનપુટ"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ઍપ શૉર્ટકટ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ઍક્સેસિબિલિટી"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"કીબોર્ડ શૉર્ટકટ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"કીબોર્ડ શૉર્ટકટને કસ્ટમાઇઝ કરો"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"શૉર્ટકટ સોંપવા માટે દી દબાવો"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"શું શૉર્ટકટ કાઢી નાખીએ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"શૉર્ટકટ સોંપવા માટે કી દબાવો"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"આ તમારા કસ્ટમ શૉર્ટકટને કાયમી રીતે ડિલીટ કરશે."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"શૉર્ટકટ શોધો"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"કોઈ શોધ પરિણામો નથી"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\'નાનું કરો\'નું આઇકન"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ઍક્શન અથવા મેટા કીનું આઇકન"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"પ્લસનું આઇકન"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"કસ્ટમાઇઝ કરો"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"થઈ ગયું"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\'મોટું કરો\'નું આઇકન"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"અથવા"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"વત્તા"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ફૉરવર્ડ સ્લૅશ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"કીબોર્ડના સેટિંગ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"શૉર્ટકટ સેટ કરો"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"કાઢી નાખો"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"રદ કરો"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"કી દબાવો"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"કી સંયોજન પેહલેથી ઉપયોગમાં છે. અન્ય કી અજમાવી જુઓ."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"કી સંયોજન પેહલેથી ઉપયોગમાં છે. અન્ય કી અજમાવી જુઓ."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"શૉર્ટકટ સેટ કરી શકાતો નથી."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"તમારા કીબોર્ડ વડે નૅવિગેટ કરો"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"કીબોર્ડ શૉર્ટકર્ટ જાણો"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"તમારા ટચપૅડ વડે નૅવિગેટ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 241e2e630013..474014ce3cea 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ इस्तेमाल करें"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट है"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ऑडियो शेयर करने की सुविधा"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ऑडियो को स्विच या शेयर करने के लिए टैप करें"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ऑडियो शेयर करने की सुविधा काम करती है"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लॉक स्क्रीन विजेट"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"किसी विजेट से कोई ऐप्लिकेशन खोलने के लिए, आपको अपनी पहचान की पुष्टि करनी होगी. ध्यान रखें कि आपके टैबलेट के लॉक होने पर भी, कोई व्यक्ति विजेट देख सकता है. ऐसा हो सकता है कि कुछ विजेट, लॉक स्क्रीन पर दिखाने के लिए न बने हों. इन्हें लॉक स्क्रीन पर जोड़ना असुरक्षित हो सकता है."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ठीक है"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेट"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"यह कई तरीकों से दिखती है, जैसे कि बातचीत वाली सूचनाओं में सबसे ऊपर, बबल के तौर पर, और लॉक स्क्रीन पर प्रोफ़ाइल फ़ोटो के तौर पर. साथ ही, यह \'परेशान न करें\' मोड को बायपास कर सकती है"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"प्राथमिकता"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर बातचीत की सुविधाएं काम नहीं करतीं"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बंडल के बारे में सुझाव/राय दें या शिकायत करें"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ये सूचनाएं नहीं बदली जा सकती हैं."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कॉल से जुड़ी सूचनाओं को ब्लॉक नहीं किया जा सकता."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"सूचनाओं के इस समूह को यहां कॉन्फ़िगर नहीं किया जा सकता"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन पर, बाईं ओर या ऊपर के ऐप पर स्विच करने के लिए"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीन के दौरान: एक ऐप्लिकेशन को दूसरे ऐप्लिकेशन से बदलें"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ऐक्टिव विंडो को एक से दूसरे डिसप्ले पर स्विच करें"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"विंडो को बाईं ओर ले जाएं"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"विंडो को दाईं ओर ले जाएं"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"विंडो को बड़ा करें"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"विंडो को छोटा करें"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"अगली भाषा पर स्विच करने के लिए"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"पिछली भाषा पर स्विच करने के लिए"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वर्ण से कम इस्तेमाल करें"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर को क्लिपबोर्ड पर कॉपी किया गया."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"क्लिपबोर्ड पर कॉपी करें."</string>
<string name="basic_status" msgid="2315371112182658176">"ऐसी बातचीत जिसमें इंटरैक्शन डेटा मौजूद नहीं है"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"बातचीत वाला विजेट"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"किसी बातचीत को होम स्क्रीन पर जोड़ने के लिए, उस बातचीत पर टैप करें"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"बेहतर रिज़ॉल्यूशन वाली फ़ोटो खींचने के लिए, फ़ोन को फ़्लिप करें"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"फ़्रंट स्क्रीन चालू है"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"डिवाइस फ़ोल्ड किया गया"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"डिवाइस अनफ़ोल्ड किया गया"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"सिस्टम से जुड़े कंट्रोल"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टम के ऐप्लिकेशन"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"मल्टीटास्किंग (एक साथ कई काम करना)"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रीन"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"इनपुट"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ऐप शॉर्टकट"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सुलभता"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"कीबोर्ड शॉर्टकट को पसंद के मुताबिक बनाएं"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"शॉर्टकट असाइन करने के लिए बटन दबाएं"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"क्या आपको शॉर्टकट हटाना है?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करने के लिए बटन दबाएं"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ऐसा करने से, आपका कस्टम शॉर्टकट हमेशा के लिए मिट जाएगा."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शॉर्टकट खोजें"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"खोज का कोई नतीजा नहीं मिला"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"छोटा करने का आइकॉन"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ऐक्शन या मेटा बटन का आइकॉन"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"प्लस का आइकॉन"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"पसंद के मुताबिक बनाएं"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"हो गया"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"बड़ा करने का आइकॉन"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"या"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"प्लस"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"फ़ॉरवर्ड स्लैश"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"खींचकर छोड़ने वाला हैंडल"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"कीबोर्ड सेटिंग"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"शॉर्टकट सेट करें"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"हटाएं"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द करें"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"बटन दबाएं"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट नहीं किया जा सकता."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"कीबोर्ड का इस्तेमाल करके नेविगेट करें"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"कीबोर्ड शॉर्टकट के बारे में जानें"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"टचपैड का इस्तेमाल करके नेविगेट करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index a179649922d2..35996ea1c1c9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zajedničko slušanje"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite za prebacivanje ili dijeljenje zvuka"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava zajedničko slušanje"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgeti na zaključanom zaslonu"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da biste otvorili aplikaciju pomoću widgeta, trebate potvrditi da ste to vi. Također napominjemo da ih svatko može vidjeti, čak i ako je vaš tablet zaključan. Neki widgeti možda nisu namijenjeni za zaključani zaslon, pa ih možda nije sigurno dodati ovdje."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Shvaćam"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgeti"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se pri vrhu obavijesti razgovora i kao profilna slika na zaključanom zaslonu, izgleda kao oblačić, prekida Ne uznemiravaj"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava značajke razgovora"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošaljite povratne informacije o paketu"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Te se obavijesti ne mogu izmijeniti."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obavijesti o pozivima ne mogu se izmijeniti."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ta se grupa obavijesti ne može konfigurirati ovdje"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prelazak na aplikaciju slijeva ili iznad uz podijeljeni zaslon"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tijekom podijeljenog zaslona: zamijeni aplikaciju drugom"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premještanje aktivnog prozora između zaslona"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Premještanje prozora ulijevo"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Premještanje prozora udesno"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimiziranje prozora"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimiziranje prozora"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Prelazak na sljedeći jezik"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prelazak na prethodni jezik"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Upotrijebite manje od ovoliko znakova: <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj međuverzije"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Broj međuverzije kopiran je u međuspremnik."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje u međuspremnik."</string>
<string name="basic_status" msgid="2315371112182658176">"Otvoreni razgovor"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgeti razgovora"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite razgovor da biste ga dodali na početni zaslon"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu razlučivost okrenite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji zaslon je uključen"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Kontrole sustava"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplikacije sustava"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Obavljanje više zadataka"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nedavne aplikacije"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Podijeljeni zaslon"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečaci aplikacija"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tipkovni prečaci"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodba tipkovnih prečaca"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipku da biste dodijelili prečac"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite li ukloniti prečac?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da biste dodijelili prečac"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Time će se vaš prilagođeni prečac trajno izbrisati."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečaci za pretraživanje"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za sažimanje"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona tipke za radnju odnosno meta tipka"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona plusa"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Prilagodi"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"kosa crta"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za povlačenje"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Postavke tipkovnice"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Postavite prečac"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Odustani"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipku"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tipki već se upotrebljava. Pokušajte s drugom tipkom."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tipki već se upotrebljava. Pokušajte s drugom tipkom."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Prečac se ne može postaviti."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tipkovnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tipkovnim prečacima"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 4b5b9162b01b..c807aee30bb1 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hang megosztása"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Koppintással átkapcsolhatja vagy megoszthatja a hangot"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Támogatja a hang megosztását"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"A lezárási képernyő moduljai"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ha modul használatával szeretne megnyitni egy alkalmazást, igazolnia kell a személyazonosságát. Ne felejtse továbbá, hogy bárki megtekintheti a modulokat, még akkor is, amikor zárolva van a táblagép. Előfordulhat, hogy bizonyos modulokat nem a lezárási képernyőn való használatra terveztek, ezért nem biztonságos a hozzáadásuk."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Értem"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Modulok"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"A beszélgetésekre vonatkozó értesítések tetején, lebegő buborékként látható, megjeleníti a profilképet a lezárási képernyőn, és megszakítja a Ne zavarjanak funkciót"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritás"</string>
<string name="no_shortcut" msgid="8257177117568230126">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> nem támogatja a beszélgetési funkciókat"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Visszajelzés küldése a csomagról"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ezeket az értesítéseket nem lehet módosítani."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"A hívásértesítéseket nem lehet módosítani."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Az értesítések jelen csoportját itt nem lehet beállítani"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Váltás a bal oldalt, illetve fent lévő appra osztott képernyő esetén"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Osztott képernyőn: az egyik alkalmazás lecserélése egy másikra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktív ablak áthelyezése egyik kijelzőről a másikra"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Ablak balra mozgatása"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Ablak mozgatása jobbra"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Ablak teljes méretre állítása"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Ablak kis méretre állítása"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Bevitel"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Váltás a következő nyelvre"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Váltás az előző nyelvre"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Legfeljebb <xliff:g id="LENGTH">%1$d</xliff:g> karaktert használhat"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildszám"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Buildszám a vágólapra másolva."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Másolás a vágólapra."</string>
<string name="basic_status" msgid="2315371112182658176">"Beszélgetés megnyitása"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Beszélgetési modulok"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Koppintson a kívánt beszélgetésre a kezdőképernyőre való felvételhez"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"A nagyobb felbontás érdekében fordítsa meg a telefont"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Előlapi képernyő bekapcsolva"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"összehajtva"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kihajtva"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Rendszervezérlők"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Rendszeralkalmazások"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Legutóbbi alkalmazások"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Osztott képernyő"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Bevitel"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Alkalmazásikonok"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kisegítő lehetőségek"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Billentyűparancsok"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"A billentyűparancsok személyre szabása"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nyomja meg a billentyűt a parancsikon hozzárendeléséhez"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Eltávolítja a billentyűparancsot?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nyomja meg a billentyűt a billentyűparancs hozzárendeléséhez"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ezzel véglegesen törli az egyéni billentyűparancsot."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Billentyűparancsok keresése"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nincsenek keresési találatok"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Összecsukás ikon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Művelet vagy Meta billentyű ikonja"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pluszikon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Személyre szabás"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Kész"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kibontás ikon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vagy"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plusz"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"törtvonal"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Fogópont"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Billentyűzetbeállítások"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Billentyűparancs beállítása"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eltávolítás"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Mégse"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nyomja le a billentyűt"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"A billentyűkombináció már használatban van. Próbálkozzon másik kulccsal."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"A billentyűkombináció már használatban van. Próbálkozzon másik billentyűvel."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nem lehet beállítani a billentyűparancsot."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigáció a billentyűzet segítségével"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Billentyűparancsok megismerése"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigálás az érintőpaddal"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index a06209867663..5603aff7f059 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Միացնել Bluetooth-ը"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Միացված է"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Աուդիոյի փոխանցում"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Հպեք՝ աուդիոն փոխանջատելու կամ դրանով կիսվելու համար"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Աջակցում է աուդիոյի փոխանցում"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Պահված է"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Կողպէկրանի վիջեթներ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Վիջեթի միջոցով հավելված բացելու համար դուք պետք է հաստատեք ձեր ինքնությունը։ Նաև նկատի ունեցեք, որ ցանկացած ոք կարող է դիտել վիջեթները, նույնիսկ երբ ձեր պլանշետը կողպված է։ Որոշ վիջեթներ կարող են նախատեսված չլինել ձեր կողպէկրանի համար, և այստեղ դրանց ավելացնելը կարող է վտանգավոր լինել։"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Եղավ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Վիջեթներ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
@@ -695,7 +700,7 @@
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s: Հպեք՝ ձայնն անջատելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s։ Հպեք՝ թրթռոցը միացնելու համար։"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s։ Հպեք՝ ձայնը անջատելու համար։"</string>
- <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի կառավարում"</string>
+ <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի վերահսկում"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Տարածական հնչողություն"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Անջատել"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Ֆիքսված"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ցուցադրվում է զրույցների ծանուցումների վերևում, ինչպես նաև կողպէկրանին որպես պրոֆիլի նկար, հայտնվում է ամպիկի տեսքով, ընդհատում է «Չանհանգստացնել» ռեժիմը"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Կարևոր"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը զրույցի գործառույթներ չի աջակցում"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Հավաքածուի մասին կարծիք հայտնել"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Այս ծանուցումները չեն կարող փոփոխվել:"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Զանգերի մասին ծանուցումները հնարավոր չէ փոփոխել։"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ծանուցումների տվյալ խումբը հնարավոր չէ կարգավորել այստեղ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Անցեք աջ կողմի կամ վերևի հավելվածին տրոհված էկրանի միջոցով"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Տրոհված էկրանի ռեժիմում մեկ հավելվածը փոխարինել մյուսով"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Տեղափոխել ակտիվ պատուհանը էկրանների միջև"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Պատուհանը տեղափոխել ձախ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Պատուհանը տեղափոխել աջ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Ծավալել պատուհանը"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Ծալել պատուհանը"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ներածում"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Անցնել հաջորդ լեզվին"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Անցնել նախորդ լեզվին"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Օգտագործեք մինչև <xliff:g id="LENGTH">%1$d</xliff:g> նիշ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Կառուցման համարը"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Կառուցման համարը պատճենվեց սեղմատախտակին։"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"պատճենել սեղմատախտակին։"</string>
<string name="basic_status" msgid="2315371112182658176">"Բաց զրույց"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Զրույցի վիջեթներ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Հպեք զրույցին՝ այն հիմնական էկրանին ավելացնելու համար"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ավելի մեծ լուծաչափի համար շրջեք հեռախոսը"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Առջևի էկրանը միացված է"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ծալված"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"բացված"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Համակարգի կառավարման տարրեր"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Համակարգային հավելվածներ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Բազմախնդրու­թյուն"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Վերջին օգտագործած հավելվածները"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Տրոհված էկրան"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Ներածում"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Հավելվածի դյուրանցումներ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Հատուկ գործառույթներ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Ստեղնային դյուրանցումներ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Կարգավորեք ստեղնային դյուրանցումներ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Սեղմեք որևէ ստեղն՝ դյուրանցում նշանակելու համար"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Հեռացնե՞լ դյուրանցումը"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Սեղմեք որևէ ստեղն՝ դյուրանցում նշանակելու համար"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ձեր հատուկ դյուրանցումն ընդմիշտ կջնջվի։"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Դյուրանցումների որոնում"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Որոնման արդյունքներ չկան"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ծալել պատկերակը"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Գործողության կամ Meta ստեղնի պատկերակ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Պլյուս պատկերակ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Կարգավորել"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Պատրաստ է"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ծավալել պատկերակը"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"կամ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"գումարած"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"շեղ գիծ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Տեղափոխման նշիչ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ստեղնաշարի կարգավորումներ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ստեղծել դյուրանցում"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Հեռացնել"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Չեղարկել"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Սեղմեք որևէ ստեղն"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք այլ ստեղն։"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք այլ ստեղն։"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Դյուրանցումը հնարավոր չէ ստեղծել։"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Կողմնորոշվեք ձեր ստեղնաշարի օգնությամբ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Սովորեք օգտագործել ստեղնային դյուրանցումները"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Կողմնորոշվեք ձեր հպահարթակի օգնությամբ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index cf9b093a1d43..3820ab749854 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Berbagi Audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ketuk untuk beralih atau berbagi audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Mendukung berbagi audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"berhenti hubungkan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget layar kunci"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Untuk membuka aplikasi menggunakan widget, Anda perlu memverifikasi diri Anda. Selain itu, harap ingat bahwa siapa saja dapat melihatnya, bahkan saat tablet Anda terkunci. Beberapa widget mungkin tidak dirancang untuk layar kunci Anda dan mungkin tidak aman untuk ditambahkan di sini."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Oke"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Muncul teratas di notifikasi percakapan dan sebagai foto profil di layar kunci, ditampilkan sebagai balon, menimpa mode Jangan Ganggu"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritas"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak mendukung fitur percakapan"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Berikan Masukan Gabungan"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Notifikasi ini tidak dapat diubah."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Notifikasi panggilan tidak dapat diubah."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Grup notifikasi ini tidak dapat dikonfigurasi di sini"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Beralih ke aplikasi di bagian kiri atau atas saat menggunakan layar terpisah"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Dalam layar terpisah: ganti salah satu aplikasi dengan yang lain"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Memindahkan jendela aktif dari satu layar ke layar lainnya"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Memindahkan jendela ke kiri"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Memindahkan jendela ke kanan"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Memaksimalkan jendela"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Meminimalkan jendela"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Beralih ke bahasa berikutnya"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Beralih ke bahasa sebelumnya"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gunakan kurang dari <xliff:g id="LENGTH">%1$d</xliff:g> karakter"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nomor build"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Nomor versi disalin ke papan klip."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"salin ke papan klip."</string>
<string name="basic_status" msgid="2315371112182658176">"Membuka percakapan"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widget Percakapan"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Ketuk percakapan untuk menambahkannya ke Layar utama"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk resolusi lebih tinggi, balik ponsel"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Layar depan diaktifkan"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ditutup"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dibuka"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Kontrol sistem"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplikasi sistem"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplikasi terbaru"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Layar terpisah"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Pintasan aplikasi"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aksesibilitas"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan keyboard"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Menyesuaikan pintasan keyboard"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tekan tombol untuk menetapkan pintasan"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Hapus pintasan?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan tombol untuk menetapkan pintasan"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan menghapus pintasan kustom Anda secara permanen."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Telusuri pintasan"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tidak ada hasil penelusuran"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon ciutkan"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikon tombol Tindakan atau Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikon plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Sesuaikan"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Selesai"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon luaskan"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"garis miring"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handel geser"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Setelan Keyboard"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setel pintasan"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Hapus"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Batal"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tekan tombol"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinasi tombol sudah digunakan. Coba tombol lain."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinasi tombol sudah digunakan. Coba tombol lain."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pintasan tidak dapat disetel."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Menggunakan keyboard untuk navigasi"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Pelajari pintasan keyboard"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Menavigasi menggunakan touchpad"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index fe32aba3d75b..931c3dc172a3 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hljóði deilt"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ýttu til að skipta um eða deila hljóði"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Styður hljóðdeilingu"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Græjur fyrir lásskjá"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Þú þarft að staðfesta að þetta sért þú til að geta opnað forrit með græju. Hafðu einnig í huga að hver sem er getur skoðað þær, jafnvel þótt spjaldtölvan sé læst. Sumar græjur eru hugsanlega ekki ætlaðar fyrir lásskjá og því gæti verið óöruggt að bæta þeim við hér."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ég skil"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Græjur"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Birtist efst í samtalstilkynningum og sem prófílmynd á lásskjánum. Birtist sem blaðra sem truflar „Ónáðið ekki“"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Forgangur"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> styður ekki samtalseiginleika"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Senda inn ábendingu um pakka"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ekki er hægt að breyta þessum tilkynningum."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Ekki er hægt að breyta tilkynningum um símtöl."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ekki er hægt að stilla þessar tilkynningar hér"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skiptu í forrit til vinstri eða fyrir ofan þegar skjáskipting er notuð"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Í skjáskiptingu: Skipta forriti út fyrir annað forrit"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Færa virkan glugga á milli skjáa"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Færa glugga til vinstri"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Færa glugga til hægri"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Stækka glugga"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minnka glugga"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Innsláttur"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Skipta yfir í næsta tungumál"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skipta yfir í fyrra tungumál"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Notaðu færri en <xliff:g id="LENGTH">%1$d</xliff:g> stafi"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Útgáfunúmer smíðar"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Útgáfunúmer smíðar afritað á klippiborð."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"afrita á klippiborð."</string>
<string name="basic_status" msgid="2315371112182658176">"Opna samtal"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Samtalsgræjur"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Ýttu á samtal til að bæta því á heimaskjáinn"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Snúðu símanum til að fá betri upplausn"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Kveikt á fremri skjá"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"samanbrotið"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opið"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Kerfisstýringar"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Kerfisforrit"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Fjölvinnsla"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nýleg forrit"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Skjáskipting"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Innsláttur"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Flýtileiðir forrita"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aðgengi"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Flýtilyklar"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sérsníddu flýtilykla"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Ýttu á lykil til að stilla flýtileið"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Fjarlægja flýtileið?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ýttu á lykil til að stilla flýtileið"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Þetta eyðir sérsniðnu flýtileiðinni varanlega."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Leita að flýtileiðum"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Engar leitarniðurstöður"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Minnka tákn"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Tákn lýsilykils (aðgerðarlykils)"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plústákn"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Sérsníða"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Lokið"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Stækka tákn"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eða"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plús"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"rétt skástrik"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dragkló"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Stillingar lyklaborðs"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Stilltu flýtileið"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjarlægja"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Hætta við"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Ýttu á lykil"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Lyklasamsetning er þegar í notkun. Prófaðu annan lykil."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Lyklasamsetningin er þegar í notkun. Prófaðu annan lykil."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ekki er hægt að stilla flýtileið."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Flettu með því að nota lyklaborðið"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Kynntu þér flýtilykla"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Flettu með því að nota snertiflötinn"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a66ea91eb396..7c2fc4f5a0ed 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -167,7 +167,7 @@
<string name="issuerecord_start_error" msgid="3402782952722871190">"Impossibile avviare la registrazione del problema"</string>
<string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
<string name="immersive_cling_description" msgid="2717426731830851921">"Per uscire, scorri verso il basso dalla parte superiore dello schermo"</string>
- <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa il Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Condivisione audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tocca per cambiare o condividere l\'audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supporta la condivisione audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget della schermata di blocco"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per aprire un\'app utilizzando un widget, dovrai verificare la tua identità. Inoltre tieni presente che chiunque può vederlo, anche quando il tablet è bloccato. Alcuni widget potrebbero non essere stati progettati per la schermata di blocco e potrebbe non essere sicuro aggiungerli qui."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
@@ -668,7 +673,7 @@
<string name="screen_pinning_toast" msgid="8177286912533744328">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Panoramica"</string>
<string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Home"</string>
<string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Per sbloccare questa app, scorri verso l\'alto e tieni premuto"</string>
- <string name="screen_pinning_positive" msgid="3285785989665266984">"OK"</string>
+ <string name="screen_pinning_positive" msgid="3285785989665266984">"Ok"</string>
<string name="screen_pinning_negative" msgid="6882816864569211666">"No, grazie"</string>
<string name="screen_pinning_start" msgid="7483998671383371313">"App bloccata"</string>
<string name="screen_pinning_exit" msgid="4553787518387346893">"App sbloccata"</string>
@@ -751,7 +756,7 @@
<string name="tuner_warning_title" msgid="7721976098452135267">"Il divertimento riservato a pochi eletti"</string>
<string name="tuner_warning" msgid="1861736288458481650">"L\'Ottimizzatore UI di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
<string name="tuner_persistent_warning" msgid="230466285569307806">"Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
- <string name="got_it" msgid="477119182261892069">"OK"</string>
+ <string name="got_it" msgid="477119182261892069">"Ok"</string>
<string name="tuner_toast" msgid="3812684836514766951">"Complimenti! L\'Ottimizzatore UI di sistema è stato aggiunto alle impostazioni."</string>
<string name="remove_from_settings" msgid="633775561782209994">"Rimuovi dalle impostazioni"</string>
<string name="remove_from_settings_prompt" msgid="551565437265615426">"Vuoi rimuovere l\'Ottimizzatore UI di sistema dalle impostazioni e smettere di utilizzare tutte le sue funzioni?"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Appare in cima alle notifiche delle conversazioni, come immagine del profilo nella schermata di blocco e sotto forma di bolla, inoltre interrompe la modalità Non disturbare"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priorità"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non supporta le funzionalità delle conversazioni"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Fornisci feedback sul bundle"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Impossibile modificare queste notifiche."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Impossibile modificare gli avvisi di chiamata."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Qui non è possibile configurare questo gruppo di notifiche"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passa all\'app a sinistra o sopra mentre usi lo schermo diviso"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Con lo schermo diviso: sostituisci un\'app con un\'altra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Sposta la finestra attiva tra gli schermi"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Sposta la finestra a sinistra"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Sposta la finestra a destra"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Ingrandisci la finestra"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Riduci a icona la finestra"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Inserimento"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Passa alla lingua successiva"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Passa alla lingua precedente"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa meno di <xliff:g id="LENGTH">%1$d</xliff:g> caratteri"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero build"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Numero build copiato negli appunti."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copia negli appunti."</string>
<string name="basic_status" msgid="2315371112182658176">"Apri conversazione"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widget di conversazione"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tocca una conversazione per aggiungerla alla schermata Home"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Gira il telefono per una maggiore risoluzione"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Schermo frontale attivato"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"Piegato"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"Non piegato"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controlli di sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"App di sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"App recenti"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Schermo diviso"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Scorciatoie app"</string>
@@ -1418,28 +1425,43 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilità"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Scorciatoie da tastiera"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizza scorciatoie da tastiera"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Premi un tasto per assegnare una scorciatoia"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Rimuovere scorciatoia?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Premi un tasto per assegnare una scorciatoia"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"La scorciatoia personalizzata verrà eliminata definitivamente."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Scorciatoie per la ricerca"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nessun risultato di ricerca"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona Comprimi"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icona tasto Azione o Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icona Più"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizza"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fine"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona Espandi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oppure"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"più"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Punto di trascinamento"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Impostazioni tastiera"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Imposta scorciatoia"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Rimuovi"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annulla"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Premi un tasto"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinazione di tasti già in uso. Prova con un altro tasto."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinazione di tasti già in uso. Prova con un altro tasto."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Impossibile impostare la scorciatoia."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviga usando la tastiera"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informazioni sulle scorciatoie da tastiera"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviga usando il touchpad"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Impara i gesti con il touchpad"</string>
<string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Naviga usando la tastiera e il touchpad"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Scopri gesti con il touchpad, scorciatoie da tastiera e altro ancora"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Impara i gesti con il touchpad, le scorciatoie da tastiera e altro ancora"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Indietro"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Vai alla schermata Home"</string>
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Visualizza app recenti"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 7b4245ee1c1e..e481a777c15a 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -306,11 +306,11 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"‏שימוש ב-Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"שיתוף אודיו"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"צריך להקיש כדי להחליף מצב או לשתף אודיו"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"תמיכה בשיתוף אודיו"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string>
- <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"הפעלה אוטומטית מחר"</string>
+ <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"הפעלה אוטומטית ביום הבא"</string>
<string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"‏תכונות כמו \'שיתוף מהיר\' ו\'איפה המכשיר שלי\' משתמשות ב-Bluetooth"</string>
<string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"‏חיבור ה-Bluetooth יופעל מחר בבוקר"</string>
<string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"שיתוף האודיו"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ווידג\'טים במסך הנעילה"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"כדי לפתוח אפליקציה באמצעות ווידג\'ט, עליך לאמת את זהותך. בנוסף, כדאי לזכור שכל אחד יכול לראות את הווידג\'טים גם כשהטאבלט שלך נעול. יכול להיות שחלק מהווידג\'טים לא נועדו למסך הנעילה ושלא בטוח להוסיף אותם לכאן."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"הבנתי"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ווידג\'טים"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
@@ -699,7 +704,7 @@
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"אודיו מרחבי"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"השבתה"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"מצב סטטי"</string>
- <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"מעקב אחר תנועות הראש"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"מעקב ראש"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"יש להקיש כדי לשנות את מצב תוכנת הצלצול"</string>
<string name="volume_ringer_mode" msgid="6867838048430807128">"מצב תוכנת הצלצול"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"השתקה"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"מוצגת בחלק העליון של קטע התראות השיחה וכתמונת פרופיל במסך הנעילה, מופיעה בבועה צפה ומפריעה במצב \'נא לא להפריע\'"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"בעדיפות גבוהה"</string>
<string name="no_shortcut" msgid="8257177117568230126">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא תומכת בתכונות השיחה"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"שליחת משוב על החבילה"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"לא ניתן לשנות את ההתראות האלה."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"לא ניתן לשנות את התראות השיחה."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"לא ניתן להגדיר כאן את קבוצת ההתראות הזו"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"מעבר לאפליקציה מימין או למעלה בזמן שימוש במסך מפוצל"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"כשהמסך מפוצל: החלפה בין אפליקציה אחת לאחרת"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"העברת החלון הפעיל בין מסכים"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"העברת החלון שמאלה"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"העברת החלון ימינה"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"הגדלת החלון"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"מזעור החלון"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"קלט"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"מעבר לשפה הבאה"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"מעבר לשפה הקודמת"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"אפשר להזין עד <xliff:g id="LENGTH">%1$d</xliff:g> תווים"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"‏מספר Build"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"‏מספר ה-Build הועתק ללוח."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"העתקה ללוח."</string>
<string name="basic_status" msgid="2315371112182658176">"פתיחת שיחה"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ווידג\'טים של שיחות"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"יש להקיש על שיחה כדי להוסיף אותה למסך הבית"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"כדי לצלם תמונה ברזולוציה גבוהה יותר, כדאי להפוך את הטלפון"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"המסך הקדמי מופעל"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"מצב מקופל"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"מצב לא מקופל"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‏%1$s‏ / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"הגדרות המערכת"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"אפליקציות מערכת"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ריבוי משימות"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"אפליקציות שהיו בשימוש לאחרונה"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"מסך מפוצל"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"קלט"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"קיצורי דרך של אפליקציות"</string>
@@ -1418,28 +1425,43 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"נגישות"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"מקשי קיצור"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"התאמה אישית של מקשי הקיצור"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"יש ללחוץ על מקש כדי להקצות מקש קיצור"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"להסיר את קיצור הדרך?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"צריך להקיש על מקש כדי להקצות מקש קיצור"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"קיצור הדרך יימחק באופן סופי."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"קיצורי דרך לחיפוש"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"אין תוצאות חיפוש"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"סמל הכיווץ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"סמל מקש הפעולה (\"מטא\")"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"סמל הפלוס"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"התאמה אישית"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"סיום"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"סמל ההרחבה"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"או"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"סימן חיבור (פלוס)"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"קו נטוי"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"נקודת האחיזה לגרירה"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"הגדרות המקלדת"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"הגדרה של מקש קיצור"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"הסרה"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ביטול"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"יש ללחוץ על מקש"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"שילוב המקשים הזה כבר בשימוש. אפשר לנסות מקש אחר."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"שילוב המקשים הזה כבר בשימוש. אפשר לנסות מקש אחר."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"לא ניתן להגדיר את קיצור הדרך."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ניווט באמצעות המקלדת"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"מידע על מקשי קיצור"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ניווט באמצעות לוח המגע"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"מידע על התנועות בלוח המגע"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"ניווט באמצעות המקלדת ולוח המגע"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"מידע על התנועות בלוח המגע, מקשי קיצור ועוד"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"ניווט עם המקלדת ולוח המגע"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"כאן אפשר לקרוא איך מפעילים תנועות בלוח המגע, מקשי קיצור ועוד"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"חזרה"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"חזרה לדף הבית"</string>
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"הצגת האפליקציות האחרונות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index c4fd62b355f6..1a2d0225c44f 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -306,11 +306,11 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音声の共有"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"タップして音声の切り替えや共有を行えます"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"音声の共有に対応しています"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存済み"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
- <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"明日自動的に ON にする"</string>
+ <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"日付が変わったら自動的に ON にする"</string>
<string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Quick Share や「デバイスを探す」などの機能は Bluetooth を使用します"</string>
<string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"明日の朝に Bluetooth が ON になります"</string>
<string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"音声を共有"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ロック画面ウィジェット"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ウィジェットを使用してアプリを起動するには、本人確認が必要です。タブレットがロックされた状態でも他のユーザーにウィジェットが表示されますので、注意してください。一部のウィジェットについてはロック画面での使用を想定していないため、ロック画面への追加は危険な場合があります。"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ウィジェット"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"会話通知の一番上に表示されると同時に、ロック画面にプロフィール写真として表示されるほか、バブルとして表示され、サイレント モードが中断されます"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>は会話機能に対応していません"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"バンドルに関するフィードバックを送信"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"これらの通知は変更できません。"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"着信通知は変更できません。"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"このグループの通知はここでは設定できません"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"分割画面の使用時に左側または上部のアプリに切り替える"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"分割画面中: アプリを順に置換する"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"アクティブなウィンドウをディスプレイ間で移動する"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ウィンドウを左に移動する"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ウィンドウを右に移動する"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ウィンドウを最大化する"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ウィンドウを最小化する"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"入力"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"次の言語に切り替える"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"前の言語に切り替える"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"使用できる文字数は <xliff:g id="LENGTH">%1$d</xliff:g> 文字未満です"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ビルド番号"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ビルド番号をクリップボードにコピーしました。"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"クリップボードにコピー。"</string>
<string name="basic_status" msgid="2315371112182658176">"空の会話"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"会話ウィジェット"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"会話をタップするとホーム画面に追加されます"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"高解像度で撮るにはスマートフォンを裏返してください"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"フロント画面が ON になりました"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折りたたんだ状態"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"広げた状態"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"システム コントロール"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"システムアプリ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"マルチタスク"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"最近使ったアプリ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割画面"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"入力"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"アプリのショートカット"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ユーザー補助"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"キーボード ショートカット"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"キーボード ショートカットをカスタマイズする"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ショートカットを割り当てるキーを押してください"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ショートカットを削除しますか?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ショートカットを割り当てるキーを押してください"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"この操作を行うと、カスタム ショートカットが完全に削除されます。"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"検索ショートカット"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"検索結果がありません"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"閉じるアイコン"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"アクションキーまたはメタキーのアイコン"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"プラスアイコン"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"カスタマイズ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完了"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"開くアイコン"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"または"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"プラス"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"スラッシュ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ドラッグ ハンドル"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"キーボードの設定"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ショートカットの設定"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"削除"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"キャンセル"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"キーを押してください"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"このキーの組み合わせはすでに使用されています。別のキーを試してください。"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"このキーの組み合わせはすでに使用されています。別のキーを試してください。"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ショートカットを設定できません。"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"キーボードを使用して移動する"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"キーボード ショートカットの詳細"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"タッチパッドを使用して移動する"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 7cb3c8a99303..d7fe2845a520 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ის გამოყენება"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"დაკავშირებული"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"აუდიოს გაზიარება"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"შეეხეთ აუდიოს გადასართავად ან გასაზიარებლად"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"აუდიოს გაზიარება მხარდაჭერილია"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"შენახული"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"დაბლოკილი ეკრანის ვიჯეტები"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"უნდა დაადასტუროთ თქვენი ვინაობა, რათა გახსნათ აპი ვიჯეტის გამოყენებით. გაითვალისწინეთ, რომ ნებისმიერს შეუძლია მათი ნახვა, მაშინაც კი, როცა ტაბლეტი დაბლოკილია. ზოგი ვიჯეტი შეიძლება არ იყოს გათვლილი თქვენი დაბლოკილი ეკრანისთვის და მათი აქ დამატება შეიძლება სახიფათო იყოს."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"გასაგებია"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ვიჯეტები"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"გამოჩნდება საუბრის შეტყობინებების თავში და პროფილის სურათის სახით ჩაკეტილ ეკრანზე, ჩნდება ბუშტის სახით, წყვეტს ფუნქციას „არ შემაწუხოთ“"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"პრიორიტეტი"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ს არ აქვს მიმოწერის ფუნქციების მხარდაჭერა"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ნაკრებზე გამოხმაურების წარმოდგენა"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ამ შეტყობინებების შეცვლა შეუძლებელია."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ზარის შეტყობინებების შეცვლა შეუძლებელია."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"შეტყობინებების ამ ჯგუფის კონფიგურირება აქ შეუძლებელია"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ეკრანის გაყოფის გამოყენებისას აპზე მარცხნივ ან ზემოთ გადართვა"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ეკრანის გაყოფის დროს: ერთი აპის მეორით ჩანაცვლება"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"აქტიური ფანჯრის გადატანა ეკრანებს შორის"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ფანჯრის მარცხნივ გადაადგილება"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ფანჯრის მარჯვნივ გადაადგილება"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ფანჯრის გაშლა"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ფანჯრის ჩაკეცვა"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"შეყვანა"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"შემდეგ ენაზე გადართვა"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"წინა ენაზე გადართვა"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"გამოიყენეთ <xliff:g id="LENGTH">%1$d</xliff:g>-ზე ნაკლები სიმბოლო"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ანაწყობის ნომერი"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ანაწყობის ნომერი დაკოპირებულია გაცვლის ბუფერში."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"კოპირება გაცვლის ბუფერში."</string>
<string name="basic_status" msgid="2315371112182658176">"მიმოწერის გახსნა"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"საუბრის ვიჯეტები"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"შეეხეთ საუბარს მის თქვენს მთავარ ეკრანზე დასამატებლად"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"მაღალი გარჩევადობისთვის ამოაბრუნეთ ტელეფონი"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"წინა ეკრანი ჩართულია"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"დაკეცილი"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"გაშლილი"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"სისტემის მართვის საშუალებები"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"სისტემის აპები"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"მრავალამოცანიანი რეჟიმი"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ბოლოდროინდელი აპები"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ეკრანის გაყოფა"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"შეყვანა"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"აპის მალსახმობები"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"მისაწვდომობა"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"კლავიატურის მალსახმობები"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"კლავიატურის მალსახმობების მორგება"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"მალსახმობის მინიჭებისთვის დააჭირეთ კლავიშს"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"გსურთ მალსახმობის წაშლა?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"მალსახმობის მინიჭებისთვის დააჭირეთ კლავიშს"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ეს თქვენს მორგებულ მალსახმობებს სამუდამოდ წაშლის."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ძიების მალსახმობები"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ძიების შედეგები არ არის"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ხატულის ჩაკეცვა"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"მოქმედების ან მეტა კლავიშის ხატულა"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"პლუსის ხატულა"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"მორგება"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"მზადაა"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ხატულის გაფართოება"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ან"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"პლუსი"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"წინ გადახრილი წილადის ხაზი"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"სახელური ჩავლებისთვის"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"კლავიატურის პარამეტრები"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"მალსახმობის დაყენება"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ამოშლა"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"გაუქმება"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"დააჭირეთ კლავიშს"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კლავიში."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კლავიში."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"მალსახმობის დაყენება ვერ ხერხდება."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ნავიგაცია კლავიატურის გამოყენებით"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"კლავიატურის მალსახმობების სწავლა"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ნავიგაცია სენსორული პანელის გამოყენებით"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 4122eeef9d59..92269a2a2e73 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Аудио бөлісу"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудионы бөлісу немесе ауыстыру үшін түртіңіз."</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Аудио бөлісуге мүмкіндік береді."</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сақталды"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Құлып экранының виджеттері"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Қолданбаны виджет көмегімен ашу үшін жеке басыңызды растауыңыз керек. Сондай-ақ басқалар оларды планшетіңіз құлыптаулы кезде де көре алатынын ескеріңіз. Кейбір виджеттер құлып экранына арналмаған болады, сондықтан оларды мұнда қосу қауіпсіз болмауы мүмкін."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Түсінікті"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеттер"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Әңгіме туралы хабарландырулардың жоғарғы жағында тұрады және құлыптаулы экранда профиль суреті болып көрсетіледі, қалқыма хабар түрінде шығады, Мазаламау режимін тоқтатады."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Маңызды"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> әңгіме функцияларын қолдамайды."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пакет туралы пікір жіберу"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Бұл хабарландыруларды өзгерту мүмкін емес."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Қоңырау туралы хабарландыруларды өзгерту мүмкін емес."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Мұндай хабарландырулар бұл жерде конфигурацияланбайды."</string>
@@ -853,7 +859,7 @@
<string name="keyboard_shortcut_a11y_filter_current_app" msgid="7944592357493737911">"Қазіргі қолданбаға арналған жылдам пәрмендер көрсетіледі."</string>
<string name="group_system_access_notification_shade" msgid="1619028907006553677">"Хабарландыруларды көру"</string>
<string name="group_system_full_screenshot" msgid="5742204844232667785">"Скриншот жасау"</string>
- <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Жылдам пәрмендерді көрсету"</string>
+ <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Пернелер тіркесімдерін көрсету"</string>
<string name="group_system_go_back" msgid="2730322046244918816">"Артқа"</string>
<string name="group_system_access_home_screen" msgid="4130366993484706483">"Негізгі экранға өту"</string>
<string name="group_system_overview_open_apps" msgid="5659958952937994104">"Соңғы қолданбаларды көру"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлінген экранда сол не жоғары жақтағы қолданбаға ауысу"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлу кезінде: бір қолданбаны басқасымен алмастыру"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Дисплейлер арасында қосулы терезені ауыстыру"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Терезені сол жаққа жылжыту"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Терезені оң жаққа жылжыту"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Терезені ұлғайту"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Терезені кішірейту"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Енгізу"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Келесі тілге ауысу"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Алдыңғы тілге ауысу"</string>
@@ -1201,7 +1211,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string>
- <string name="media_input_group_title" msgid="2057057473860783021">"Кіріс"</string>
+ <string name="media_input_group_title" msgid="2057057473860783021">"Енгізу"</string>
<string name="media_output_group_title" msgid="6789001895863332576">"Шығыс"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Мультимедиа файлын басқа құрылғыға жылжыту үшін ортақ сеансты тоқтатыңыз."</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Тоқтату"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Ең көбі <xliff:g id="LENGTH">%1$d</xliff:g> таңба пайдаланыңыз."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Құрама нөмірі"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Құрама нөмірі буферге көшірілді."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"буферге көшіріңіз."</string>
<string name="basic_status" msgid="2315371112182658176">"Ашық әңгіме"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Әңгіме виджеттері"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Негізгі экранға қосқыңыз келетін әңгімені түртіңіз."</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жоғары ажыратымдылық үшін телефонды айналдырыңыз."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Алдыңғы экран қосылды."</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"жабық"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ашық"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,30 +1418,44 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Жүйені басқару элементтері"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Жүйелік қолданбалар"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Мультитаскинг"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Соңғы қолданбалар"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Экранды бөлу"</string>
- <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Кіріс"</string>
+ <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Енгізу"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Қолданба таңбашалары"</string>
<string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Қолданыстағы қолданба"</string>
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Арнайы мүмкіндіктер"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Перне тіркесімдері"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Пернелер тіркесімін бейімдеу"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Жылдам пәрменді тағайындау үшін пернені басыңыз."</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Жылдам пәрменді өшіру керек пе?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Жылдам пәрменді тағайындау үшін пернені басыңыз."</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Арнаулы жылдам пәрменіңіз біржола жойылады."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Іздеу жылдам пәрмендері"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Іздеу нәтижелері жоқ."</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жию белгішесі"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Әрекет немесе Meta пернесінің белгішесі"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Қосу белгішесі"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Бейімдеу"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Дайын"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жаю белгішесі"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"немесе"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"қосу"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"қиғаш сызық"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Сүйрейтін тетік"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Пернетақта параметрлері"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Жылдам пәрменді орнату"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Өшіру"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Бас тарту"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Пернені басыңыз"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Бұл пернелер тіркесімі қазір қолданыста. Басқа пернені таңдаңыз."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Бұл пернелер тіркесімі қазір қолданыста. Басқа перне таңдаңыз."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Перне тіркесімін орнату мүмкін емес."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Пернетақтамен жұмыс істеңіз"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Перне тіркесімдерін үйреніңіз."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Сенсорлық тақтамен жұмыс істеңіз"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index f9ec58f2fba8..13153c61ab46 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ប្រើប៊្លូធូស"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"បានភ្ជាប់"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ការស្ដាប់សំឡេងរួមគ្នា"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ចុចដើម្បីប្ដូរ ឬចែករំលែកសំឡេង"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"អាចប្រើការស្ដាប់សំឡេងរួមគ្នា"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"បាន​រក្សាទុក"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ធាតុ​ក្រាហ្វិកលើអេក្រង់ចាក់សោ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ដើម្បីបើកកម្មវិធីដោយប្រើធាតុ​ក្រាហ្វិក អ្នកនឹងត្រូវផ្ទៀងផ្ទាត់ថាជាអ្នក។ ទន្ទឹមនឹងនេះ សូមចងចាំថា នរណាក៏អាចមើលធាតុក្រាហ្វិកបាន សូម្បីពេលថេប្លេតរបស់អ្នកជាប់សោក៏ដោយ។ ធាតុ​ក្រាហ្វិកមួយចំនួនប្រហែលមិនត្រូវបានរចនាឡើងសម្រាប់អេក្រង់ចាក់សោរបស់អ្នកទេ និងមិនមានសុវត្ថិភាពឡើយ បើបញ្ចូលទៅទីនេះ។"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"យល់ហើយ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ធាតុ​ក្រាហ្វិក"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរ​អ្នក​ប្រើ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយ​ទាញចុះ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យ​ទាំងអស់​ក្នុង​វគ្គ​នេះ​នឹង​ត្រូវ​លុប។"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"បង្ហាញនៅខាងលើ​ការជូនដំណឹងអំពីការសន្ទនា និងជារូបភាព​កម្រង​ព័ត៌មាននៅលើអេក្រង់ចាក់សោ បង្ហាញជាពពុះ បង្អាក់មុខងារកុំ​រំខាន"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"អាទិភាព"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> មិនអាចប្រើ​មុខងារ​សន្ទនា​បានទេ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ផ្ដល់មតិកែលម្អជាកញ្ចប់"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"មិនអាច​កែប្រែ​ការជូនដំណឹង​ទាំងនេះ​បានទេ។"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"មិនអាច​កែប្រែ​ការជូនដំណឹងអំពីការហៅទូរសព្ទបានទេ។"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"មិនអាច​កំណត់​រចនាសម្ព័ន្ធ​ក្រុមការជូនដំណឹងនេះ​នៅទីនេះ​បានទេ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ប្ដូរទៅកម្មវិធីនៅខាងឆ្វេង ឬខាងលើ ពេលកំពុងប្រើមុខងារ​បំបែកអេក្រង់"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ក្នុងអំឡុងពេលប្រើមុខងារបំបែកអេក្រង់៖ ជំនួសកម្មវិធីពីមួយទៅមួយទៀត"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ផ្លាស់ទីវិនដូដែលសកម្មរវាងផ្ទាំងអេក្រង់"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ផ្លាស់ទីវិនដូទៅឆ្វេង"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ផ្លាស់ទីវិនដូទៅស្ដាំ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ពង្រីកវិនដូ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"បង្រួមវិនដូ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"បញ្ចូល"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ប្ដូរទៅភាសាបន្ទាប់"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ប្ដូរទៅភាសាមុន"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ប្រើតិចជាង <xliff:g id="LENGTH">%1$d</xliff:g> តួអក្សរ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"លេខ​កំណែបង្កើត"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"បានចម្លងលេខ​កំណែបង្កើតទៅឃ្លីបបត។"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ចម្លង​ទៅ​ឃ្លីបបត។"</string>
<string name="basic_status" msgid="2315371112182658176">"បើកការសន្ទនា"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ធាតុ​ក្រាហ្វិកនៃការសន្ទនា"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ចុចការសន្ទនា ដើម្បីបញ្ចូលវាទៅក្នុងអេក្រង់ដើមរបស់អ្នក"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"សម្រាប់កម្រិតគុណភាពកាន់តែខ្ពស់ សូមត្រឡប់ទូរសព្ទ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"បានបើកអេក្រង់ខាងមុខ"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"បត់"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"លា"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ការគ្រប់គ្រង​ប្រព័ន្ធ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"កម្មវិធី​ប្រព័ន្ធ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ការធ្វើកិច្ចការច្រើន"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"កម្មវិធី​ថ្មីៗ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"មុខងារ​បំបែកអេក្រង់"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ធាតុចូល"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ផ្លូវកាត់​កម្មវិធី"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ភាពងាយស្រួល"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"ផ្លូវកាត់​ក្ដារ​ចុច"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ប្ដូរ​ផ្លូវកាត់​ក្ដារ​ចុចតាម​បំណង"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ចុចគ្រាប់ចុច ដើម្បីកំណត់ផ្លូវ​កាត់"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ដក​ផ្លូវកាត់​ចេញឬ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ចុចគ្រាប់ចុច ដើម្បីកំណត់ផ្លូវ​កាត់"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ការធ្វើបែបនេះនឹងលុបផ្លូវ​កាត់ផ្ទាល់ខ្លួនរបស់អ្នកជាអចិន្ត្រៃយ៍។"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ស្វែងរកផ្លូវ​កាត់"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"គ្មាន​លទ្ធផល​ស្វែងរក​ទេ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"រូបតំណាង \"បង្រួម\""</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"រូបគ្រាប់ចុចសកម្មភាព ឬមេតា"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"រូបសញ្ញាបូក"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ប្ដូរ​តាម​បំណង"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"រួចរាល់"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"រូបតំណាង \"ពង្រីក\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ឬ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"បូក"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"សញ្ញា (/)"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ដង​អូស"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ការកំណត់​ក្ដារចុច"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"កំណត់ផ្លូវ​កាត់"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ដកចេញ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"បោះបង់"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ចុចគ្រាប់ចុច"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"កំពុងប្រើបន្សំគ្រាប់ចុចស្រាប់ហើយ។ សាកល្បងប្រើគ្រាប់ចុចផ្សេង។"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"កំពុងប្រើបន្សំគ្រាប់ចុចស្រាប់ហើយ។ សាកល្បងប្រើគ្រាប់ចុចផ្សេង។"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"មិនអាចកំណត់ផ្លូវកាត់បានទេ។"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"រុករកដោយប្រើក្ដារចុចរបស់អ្នក"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ស្វែងយល់អំពីផ្លូវកាត់​ក្ដារ​ចុច"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"រុករកដោយប្រើផ្ទាំងប៉ះរបស់អ្នក"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index f3155704c55f..b83e9576edca 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ಬ್ಲೂಟೂತ್ ಬಳಸಿ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ಆಡಿಯೊವನ್ನು ಬದಲಾಯಿಸಲು ಅಥವಾ ಹಂಚಿಕೊಳ್ಳಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುತ್ತದೆ"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ಲಾಕ್ ಸ್ಕ್ರೀನ್ ವಿಜೆಟ್‌ಗಳು"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ವಿಜೆಟ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ತೆರೆಯಲು, ಇದು ನೀವೇ ಎಂದು ನೀವು ದೃಢೀಕರಿಸಬೇಕಾಗುತ್ತದೆ. ಅಲ್ಲದೆ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ ಲಾಕ್ ಆಗಿದ್ದರೂ ಸಹ ಯಾರಾದರೂ ಅವುಗಳನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಎಂಬುದನ್ನು ನೆನಪಿನಲ್ಲಿಡಿ. ಕೆಲವು ವಿಜೆಟ್‌ಗಳು ನಿಮ್ಮ ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಉದ್ದೇಶಿಸದೇ ಇರಬಹುದು ಮತ್ತು ಇಲ್ಲಿ ಸೇರಿಸುವುದು ಸುರಕ್ಷಿತವಲ್ಲದಿರಬಹುದು."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ಅರ್ಥವಾಯಿತು"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ವಿಜೆಟ್‌ಗಳು"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್‌ಡೌನ್ ಮೆನು"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಶನ್‌ನಲ್ಲಿನ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ಸಂಭಾಷಣೆ ಅಧಿಸೂಚನೆಗಳ ಮೇಲ್ಭಾಗದಲ್ಲಿ ಹಾಗೂ ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ನ ಮೇಲೆ ಪ್ರೊಫೈಲ್ ಚಿತ್ರವಾಗಿ ತೋರಿಸುತ್ತದೆ, ಬಬಲ್‌ನಂತೆ ಗೋಚರಿಸುತ್ತದೆ, ಅಡಚಣೆ ಮಾಡಬೇಡ ಮೋಡ್‌ಗೆ ಅಡ್ಡಿಯುಂಟುಮಾಡುತ್ತದೆ"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ಆದ್ಯತೆ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"ಸಂವಾದ ಫೀಚರ್‌ಗಳನ್ನು <xliff:g id="APP_NAME">%1$s</xliff:g> ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ಬಂಡಲ್‌ ಫೀಡ್‌ಬ್ಯಾಕ್‌ ಅನ್ನು ಒದಗಿಸಿ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ಈ ಅಧಿಸೂಚನೆಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ಕರೆ ಅಧಿಸೂಚನೆಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ಈ ಗುಂಪಿನ ಅಧಿಸೂಚನೆಗಳನ್ನು ಇಲ್ಲಿ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲಾಗಿರುವುದಿಲ್ಲ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸಿ ಮೋಡ್ ಬಳಸುವಾಗ ಎಡಭಾಗ ಅಥವಾ ಮೇಲ್ಭಾಗದಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸುವ ಸಮಯದಲ್ಲಿ: ಒಂದು ಆ್ಯಪ್‌ನಿಂದ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ಸಕ್ರಿಯ ವಿಂಡೋವನ್ನು ಡಿಸ್‌ಪ್ಲೇಗಳ ನಡುವೆ ಸರಿಸಿ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ವಿಂಡೋವನ್ನು ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ವಿಂಡೋವನ್ನು ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ವಿಂಡೋವನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ವಿಂಡೋವನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ಇನ್‌ಪುಟ್"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ಮುಂದಿನ ಭಾಷೆಗೆ ಬದಲಿಸಿ"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ಹಿಂದಿನ ಭಾಷೆಗೆ ಬದಲಿಸಿ"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> ಕ್ಕಿಂತ ಕಡಿಮೆ ಅಕ್ಷರಗಳನ್ನು ಬಳಸಿ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆಯನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ ನಕಲಿಸಲಾಗಿದೆ."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ಕಾಪಿ ಮಾಡಿ."</string>
<string name="basic_status" msgid="2315371112182658176">"ಸಂಭಾಷಣೆಯನ್ನು ತೆರೆಯಿರಿ"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ಸಂಭಾಷಣೆ ವಿಜೆಟ್‌ಗಳು"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ಸಂಭಾಷಣೆಯನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಲು ಅದನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ಅಧಿಕ ರೆಸಲ್ಯೂಷನ್‌ಗಾಗಿ, ಫೋನ್ ಅನ್ನು ಫ್ಲಿಪ್ ಮಾಡಿ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ಫ್ರಂಟ್ ಸ್ಕ್ರೀನ್ ಆನ್ ಆಗಿದೆ"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ಫೋಲ್ಡ್ ಮಾಡಿರುವುದು"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಿರುವುದು"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ಸಿಸ್ಟಂ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ಸಿಸ್ಟಂ ಆ್ಯಪ್‌ಗಳು"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ಮಲ್ಟಿಟಾಸ್ಕಿಂಗ್"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್‌ಗಳು"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ಇನ್‌ಪುಟ್"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ಆ್ಯಪ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ನಿಯೋಜಿಸಲು ಕೀಯನ್ನು ಒತ್ತಿರಿ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬೇಕೇ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ನಿಯೋಜಿಸಲು ಕೀಯನ್ನು ಒತ್ತಿರಿ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ಇದು ನಿಮ್ಮ ಕಸ್ಟಮ್ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸುತ್ತದೆ."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ಹುಡುಕಾಟದ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ಯಾವುದೇ ಹುಡುಕಾಟ ಫಲಿತಾಂಶಗಳಿಲ್ಲ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ಕುಗ್ಗಿಸುವ ಐಕಾನ್"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ಆ್ಯಕ್ಷನ್ ಅಥವಾ ಮೆಟಾ ಕೀ ಐಕಾನ್"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ಪ್ಲಸ್ ಐಕಾನ್"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ಮುಗಿದಿದೆ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ವಿಸ್ತೃತಗೊಳಿಸುವ ಐಕಾನ್"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ಅಥವಾ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ಪ್ಲಸ್"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ಫಾರ್ವರ್ಡ್ ಸ್ಲ್ಯಾಷ್"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ಡ್ರ್ಯಾಗ್‌ ಹ್ಯಾಂಡಲ್‌"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ಕೀಬೋರ್ಡ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ಶಾರ್ಟ್‌ಕಟ್ ಸೆಟ್ ಮಾಡಿ"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ತೆಗೆದುಹಾಕಿ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ರದ್ದುಮಾಡಿ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ಕೀ ಅನ್ನು ಒತ್ತಿರಿ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಕೀ ಬಳಸಿ ನೋಡಿ."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಕೀ ಬಳಸಿ ನೋಡಿ."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಸೆಟ್‌ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಕಲಿಯಿರಿ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ನಿಮ್ಮ ಟಚ್‌ಪ್ಯಾಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 931066b3d749..00b2450881fd 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"블루투스 사용"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"연결됨"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"오디오 공유"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"오디오를 전환하거나 공유하려면 탭하세요"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"오디오 공유 지원"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"저장됨"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"잠금 화면 위젯"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"위젯을 사용하여 앱을 열려면 본인 인증을 해야 합니다. 또한 태블릿이 잠겨 있더라도 누구나 볼 수 있다는 점을 유의해야 합니다. 일부 위젯은 잠금 화면에 적합하지 않고 여기에 추가하기에 안전하지 않을 수 있습니다."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"확인"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"위젯"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"대화 알림 상단에 표시, 잠금 화면에 프로필 사진으로 표시, 대화창으로 표시, 방해 금지 모드를 무시함"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"우선순위"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱은 대화 기능을 지원하지 않습니다."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"번들 관련 의견 보내기"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"이 알림은 수정할 수 없습니다."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"전화 알림은 수정할 수 없습니다."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"이 알림 그룹은 여기에서 설정할 수 없습니다."</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"화면 분할을 사용하는 중에 왼쪽 또는 위쪽에 있는 앱으로 전환하기"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"화면 분할 중: 다른 앱으로 바꾸기"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"디스플레이 간 활성 창 이동"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"창을 왼쪽으로 이동"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"창을 오른쪽으로 이동"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"창 최대화"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"창 최소화"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"입력"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"다음 언어로 전환"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"이전 언어로 전환"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>자 미만이어야 합니다."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"빌드 번호"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"빌드 번호가 클립보드에 복사되었습니다."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"클립보드에 복사"</string>
<string name="basic_status" msgid="2315371112182658176">"대화 열기"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"대화 위젯"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"대화를 탭하여 홈 화면에 추가하세요."</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"해상도를 높이려면 후면 카메라를 사용하세요."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"전면 화면 켜짐"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"접은 상태"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"펼친 상태"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"시스템 컨트롤"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"시스템 앱"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"멀티태스킹"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"최근 앱"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"화면 분할"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"입력"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"앱 단축키"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"접근성"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"단축키"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"단축키 맞춤설정"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"키를 눌러 단축키 지정"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"바로가기를 제거하시겠습니까?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"키를 눌러 단축키 지정"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"맞춤 단축어가 영구적으로 삭제됩니다."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"검색 바로가기"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"검색 결과 없음"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"접기 아이콘"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"작업 또는 메타 키 아이콘"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"더하기 아이콘"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"맞춤설정"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"완료"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"확장 아이콘"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"또는"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"더하기"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"슬래시"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"드래그 핸들"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"키보드 설정"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"단축키 설정"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"삭제"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"취소"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"키를 누르세요."</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"이미 사용 중인 키 조합입니다. 다른 키를 사용해 보세요."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"이미 사용 중인 키 조합입니다. 다른 키를 사용해 보세요."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"단축키를 설정할 수 없습니다."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"키보드를 사용하여 이동"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키에 관해 알아보세요."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"터치패드를 사용하여 이동"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index fc16164b6874..a857f4ecb3b4 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Иштетүү"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Туташты"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Чогуу угуу"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудиону которуштуруу же бөлүшүү үчүн таптаңыз"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Чогуу угууга болот"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сакталды"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Кулпуланган экрандагы виджеттер"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Колдонмону виджет аркылуу ачуу үчүн өзүңүздү ырасташыңыз керек. Алар кулпуланган планшетиңизде да көрүнүп турат. Кээ бир виджеттерди кулпуланган экранда колдоно албайсыз, андыктан аларды ал жерге кошпой эле койгонуңуз оң."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Түшүндүм"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеттер"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Cүйлөшүүлөр тууралуу билдирмелердин жогору жагында жана кулпуланган экранда профилдин сүрөтү, ошондой эле калкып чыкма билдирме түрүндө көрүнүп, \"Тынчымды алба\" режимин токтотот"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Маанилүүлүгү"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда оозеки сүйлөшкөнгө болбойт"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Топтом тууралуу пикир билдирүү"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Бул билдирмелерди өзгөртүүгө болбойт."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Чалуу билдирмелерин өзгөртүүгө болбойт."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Бул билдирмелердин тобун бул жерде конфигурациялоого болбойт"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлүнгөн экранды колдонуп жатканда сол же жогору жактагы колдонмого которулуңуз"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлүү режиминде бир колдонмону экинчисине алмаштыруу"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Активдүү терезени экрандардын ортосунда жылдыруу"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Терезени солго жылдыруу"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Терезени оңго жылдыруу"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Терезени чоңойтуу"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Терезени кичирейтүү"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Киргизүү"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Кийинки тилге которулуу"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Мурунку тилге которулуу"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> символдон ашпашы керек"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Курама номери"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Курама номери алмашуу буферине көчүрүлдү."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"алмашуу буферине көчүрүңүз."</string>
<string name="basic_status" msgid="2315371112182658176">"Ачык сүйлөшүү"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Сүйлөшүүлөр виджеттери"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Сүйлөшүүнү башкы экранга кошуу үчүн таптап коюңуз"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жогорку дааналык үчүн телефондун арткы камерасын колдонуңуз"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Маңдайкы экран күйгүзүлдү"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"бүктөлгөн"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ачылган"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Системанын башкаруу элементтери"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системанын колдонмолору"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Бир нече тапшырма аткаруу"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Акыркы колдонмолор"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Экранды бөлүү"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Киргизүү"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Колдонмонун ыкчам баскычтары"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Атайын мүмкүнчүлүктөр"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Ыкчам баскычтар"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Ыкчам баскычтарды ыңгайлаштыруу"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Ыкчам баскычты дайындоо үчүн баскычты басыңыз"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ыкчам баскыч өчүрүлсүнбү?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ыкчам баскычты дайындоо үчүн баскычты басыңыз"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ушуну менен жеке ыкчам баскычыңыз биротоло өчүрүлөт."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ыкчам баскычтарды издөө"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Эч нерсе табылган жок"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жыйыштыруу сүрөтчөсү"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Аракет же Мета ачкыч сүрөтчөсү"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Кошуу сүрөтчөсү"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Ыңгайлаштыруу"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Бүттү"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жайып көрсөтүү сүрөтчөсү"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"же"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"кошуу"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"жантык сызык"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Cүйрөө маркери"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Баскычтоп параметрлери"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ыкчам баскычты тууралоо"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Өчүрүү"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Баскычты басыңыз"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ачкыч айкалышы колдонулууда. Башка ачкычты байкап көрүңүз."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ачкычтардын айкалышы колдонулууда. Башка ачкычты байкап көрүңүз."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ыкчам баскычты коюу мүмкүн эмес."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Керектүү нерселерге баскычтоп аркылуу өтүү"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ыкчам баскычтар тууралуу билип алыңыз"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Керектүү жерге сенсордук такта аркылуу өтөсүз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 3a8c682bce0e..19b216d6975c 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ໃຊ້ Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ການແບ່ງປັນສຽງ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ແຕະເພື່ອສະຫຼັບ ຫຼື ແບ່ງປັນສຽງ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ຮອງຮັບການແບ່ງປັນສຽງ"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ບັນທຶກແລ້ວ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ວິດເຈັດໃນໜ້າຈໍລັອກ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ເພື່ອເປີດແອັບໂດຍໃຊ້ວິດເຈັດ, ທ່ານຈະຕ້ອງຢັ້ງຢືນວ່າແມ່ນທ່ານ. ນອກຈາກນັ້ນ, ກະລຸນາຮັບຊາບວ່າທຸກຄົນສາມາດເບິ່ງຂໍ້ມູນດັ່ງກ່າວໄດ້, ເຖິງແມ່ນວ່າແທັບເລັດຂອງທ່ານຈະລັອກຢູ່ກໍຕາມ. ວິດເຈັດບາງຢ່າງອາດບໍ່ໄດ້ມີໄວ້ສຳລັບໜ້າຈໍລັອກຂອງທ່ານ ແລະ ອາດບໍ່ປອດໄພທີ່ຈະເພີ່ມໃສ່ບ່ອນນີ້."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ເຂົ້າໃຈແລ້ວ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ວິດເຈັດ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯ​ແລະ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ໃນ​ເຊດ​ຊັນ​ນີ້​ຈະ​ຖືກ​ລຶບ​ອອກ."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ສະແດງຢູ່ເທິງສຸດຂອງການແຈ້ງເຕືອນການສົນທະນາ ແລະ ເປັນຮູບໂປຣໄຟລ໌ຢູ່ໜ້າຈໍລັອກ, ປາກົດເປັນຟອງ, ສະແດງໃນໂໝດຫ້າມລົບກວນໄດ້"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ສຳຄັນ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ຮອງຮັບຄຸນສົມບັດການສົນທະນາ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ໃຫ້ຄຳຕິຊົມເປັນຊຸດ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ບໍ່ສາມາດແກ້ໄຂການແຈ້ງເຕືອນເຫຼົ່ານີ້ໄດ້."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ບໍ່ສາມາດແກ້ໄຂການແຈ້ງເຕືອນການໂທໄດ້."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ບໍ່ສາມາດຕັ້ງຄ່າກຸ່ມການແຈ້ງເຕືອນນີ້ຢູ່ບ່ອນນີ້ໄດ້"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ສະຫຼັບໄປໃຊ້ແອັບຢູ່ຊ້າຍ ຫຼື ທາງເທິງໃນຂະນະທີ່ໃຊ້ແບ່ງໜ້າຈໍ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ໃນລະຫວ່າງແບ່ງໜ້າຈໍ: ໃຫ້ປ່ຽນຈາກແອັບໜຶ່ງເປັນອີກແອັບໜຶ່ງ"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ຍ້າຍໜ້າຈໍທີ່ເປີດຢູ່ໄປມາລະຫວ່າງຈໍສະແດງຜົນຕ່າງໆ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ຍ້າຍໜ້າຈໍໄປທາງຊ້າຍ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ຍ້າຍໜ້າຈໍໄປທາງຂວາ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ຂະຫຍາຍໜ້າຈໍຂຶ້ນ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ຫຍໍ້ໜ້າຈໍລົງ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ການປ້ອນຂໍ້ມູນ"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ສະຫຼັບເປັນພາສາຖັດໄປ"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ສະຫຼັບເປັນພາສາກ່ອນໜ້າ"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ໃຊ້ໜ້ອຍກວ່າ <xliff:g id="LENGTH">%1$d</xliff:g> ຕົວອັກສອນ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ໝາຍເລກສ້າງ"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ສຳເນົາໝາຍເລກສ້າງໄປໃສ່ຄລິບບອດແລ້ວ."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ສຳເນົາໄປໃສ່ຄລິບບອດ."</string>
<string name="basic_status" msgid="2315371112182658176">"ເປີດການສົນທະນາ"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ວິດເຈັດການສົນທະນາ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ແຕະໃສ່ການສົນທະນາໃດໜຶ່ງເພື່ອເພີ່ມມັນໃສ່ໂຮມສະກຣີນຂອງທ່ານ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ເພື່ອຄວາມລະອຽດທີ່ສູງຂຶ້ນ, ໃຫ້ປີ້ນໂທລະສັບ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ເປີດໜ້າຈໍດ້ານໜ້າແລ້ວ"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ພັບແລ້ວ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ກາງອອກແລ້ວ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ການຄວບຄຸມລະບົບ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ແອັບລະບົບ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ການເຮັດຫຼາຍໜ້າວຽກພ້ອມກັນ"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ແອັບຫຼ້າສຸດ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ແບ່ງໜ້າຈໍ"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ອິນພຸດ"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ທາງລັດແອັບ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"ຄີລັດ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ປັບແຕ່ງຄີລັດ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ກົດປຸ່ມເພື່ອກໍານົດທາງລັດ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ລຶບທາງລັດອອກບໍ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ກົດປຸ່ມເພື່ອກຳນົດທາງລັດ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ການດຳເນີນການນີ້ຈະລຶບທາງລັດທີ່ກຳນົດເອງຂອງທ່ານຢ່າງຖາວອນ."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ທາງລັດການຊອກຫາ"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ບໍ່ມີຜົນການຊອກຫາ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ໄອຄອນຫຍໍ້ລົງ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ໄອຄອນຄຳສັ່ງ ຫຼື ປຸ່ມ Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ໄອຄອນໝາຍບວກ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ປັບແຕ່ງ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ແລ້ວໆ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ໄອຄອນຂະຫຍາຍ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ຫຼື"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ບວກ"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ເຄື່ອງໝາຍທັບອຽງໄປໜ້າ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ບ່ອນຈັບລາກ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ການຕັ້ງຄ່າແປ້ນພິມ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ຕັ້ງທາງລັດ"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ລຶບອອກ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ຍົກເລີກ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ກົດປຸ່ມ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ໃຫ້ລອງປຸ່ມອື່ນ."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ໃຫ້ລອງປຸ່ມອື່ນ."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ຕັ້ງທາງລັດບໍ່ໄດ້."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ນຳທາງໂດຍໃຊ້ແປ້ນພິມຂອງທ່ານ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ສຶກສາຄີລັດ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ນຳທາງໂດຍໃຊ້ແຜ່ນສຳຜັດຂອງທ່ານ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index ae85ed2060c6..6d4c775ab143 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Garso įrašų bendrinimas"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Palieskite, jei norite perjungti arba bendrinti garsą"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Palaikomas garso įrašų bendrinimas"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Užrakinimo ekrano valdikliai"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Kad galėtumėte atidaryti programą naudodami valdiklį, turėsite patvirtinti savo tapatybę. Be to, atminkite, kad bet kas gali peržiūrėti valdiklius net tada, kai planšetinis kompiuteris užrakintas. Kai kurie valdikliai gali būti neskirti jūsų užrakinimo ekranui ir gali būti nesaugu juos čia pridėti."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Supratau"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Valdikliai"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
@@ -674,7 +679,7 @@
<string name="screen_pinning_exit" msgid="4553787518387346893">"Programa atsegta"</string>
<string name="stream_voice_call" msgid="7468348170702375660">"Skambutis"</string>
<string name="stream_system" msgid="7663148785370565134">"Sistema"</string>
- <string name="stream_ring" msgid="7550670036738697526">"Skambutis"</string>
+ <string name="stream_ring" msgid="7550670036738697526">"Skambėjimas"</string>
<string name="stream_music" msgid="2188224742361847580">"Medija"</string>
<string name="stream_alarm" msgid="16058075093011694">"Signalas"</string>
<string name="stream_notification" msgid="7930294049046243939">"Pranešimas"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Rodoma pokalbių pranešimų viršuje ir kaip profilio nuotrauka užrakinimo ekrane, burbule, pertraukia netrukdymo režimą"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritetiniai"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ nepalaiko pokalbių funkcijų"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pateikti atsiliepimą apie rinkinį"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Šių pranešimų keisti negalima."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Skambučių pranešimų keisti negalima."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Šios grupės pranešimai čia nekonfigūruojami"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Perjunkite į programą kairėje arba viršuje išskaidyto ekrano režimu"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Išskaidyto ekrano režimu: pakeisti iš vienos programos į kitą"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktyvaus lango perkėlimas iš vieno ekrano į kitą"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Perkelti langą į kairę"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Perkelti langą į dešinę"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Padidinti langą"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Sumažinti langą"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Įvestis"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Perjungti į kitą kalbą"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Perjungti į ankstesnę kalbą"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Naudokite daugiausia <xliff:g id="LENGTH">%1$d</xliff:g> simb."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijos numeris"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Versijos numeris nukopijuotas į iškarpinę."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopijuoti į iškarpinę"</string>
<string name="basic_status" msgid="2315371112182658176">"Atidaryti pokalbį"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Pokalbio valdikliai"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Palieskite pokalbį, kad pridėtumėte jį prie pagrindinio ekrano"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kad raiška būtų geresnė, apverskite telefoną"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Lankstomasis įrenginys išlankstomas"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Priekinis ekranas įjungtas"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sulenkta"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"nesulenkta"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistemos valdikliai"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistemos programos"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Kelių užduočių atlikimas"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Naujausios programos"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Išskaidyto ekrano režimas"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Įvestis"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Programos spartieji klavišai"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pritaikomumas"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Spartieji klavišai"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sparčiųjų klavišų tinkinimas"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Paspauskite klavišą, kad priskirtumėte spartųjį klavišą"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Pašalinti spartųjį klavišą?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Paspauskite klavišą, kad priskirtumėte spartųjį klavišą"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bus visam laikui ištrintas tinkintas spartusis klavišas."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ieškoti sparčiųjų klavišų"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nėra jokių paieškos rezultatų"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sutraukimo piktograma"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Veiksmo arba metaduomenų klavišo piktograma"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pliuso piktograma"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Tinkinti"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Atlikta"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Išskleidimo piktograma"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"arba"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"pliusas"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"dešininis pasvirasis brūkšnys"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkimo rankenėlė"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatūros nustatymai"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nustatyti spartųjį klavišą"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Pašalinti"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Atšaukti"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Paspauskite klavišą"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą klavišą."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą klavišą."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Sparčiojo klavišo nustatyti negalima."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naršykite naudodamiesi klaviatūra"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Sužinokite apie sparčiuosius klavišus"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naršykite naudodamiesi jutikline dalimi"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index b20ab36a3896..d5229462285f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio kopīgošana"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Pieskarieties, lai pārslēgtu vai kopīgotu audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Atbalsta audio kopīgošanu"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Bloķēšanas ekrāna logrīki"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Lai atvērtu lietotni, izmantojot logrīku, jums būs jāapstiprina sava identitāte. Turklāt ņemiet vērā, ka ikviens var skatīt logrīkus, pat ja planšetdators ir bloķēts. Iespējams, daži logrīki nav paredzēti izmantošanai bloķēšanas ekrānā, un var nebūt droši tos šeit pievienot."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Labi"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Logrīki"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Parādās sarunu paziņojumu augšdaļā un kā profila attēls bloķēšanas ekrānā, arī kā burbulis, pārtrauc režīmu “Netraucēt”."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritārs"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Lietotnē <xliff:g id="APP_NAME">%1$s</xliff:g> netiek atbalstītas sarunu funkcijas."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Sniegt atsauksmes par paziņojumu grupu"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Šos paziņojumus nevar modificēt."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Paziņojumus par zvaniem nevar modificēt."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Šeit nevar konfigurēt šo paziņojumu grupu."</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pāriet uz lietotni pa kreisi/augšā, kamēr izmantojat sadalīto ekrānu."</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ekrāna sadalīšanas režīmā: pārvietot lietotni no viena ekrāna uz otru"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Pārvietot aktīvo logu starp displejiem"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Pārvietot logu pa kreisi"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Pārvietot logu pa labi"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimizēt logu"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizēt logu"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ievade"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Pārslēgt uz nākamo valodu"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Pārslēgt uz iepriekšējo valodu"</string>
@@ -1218,10 +1228,9 @@
<string name="media_output_broadcast_last_update_error" msgid="5484328807296895491">"Nevar saglabāt."</string>
<string name="media_output_broadcast_code_hint_no_less_than_min" msgid="4663836092607696185">"Izmantojiet vismaz 4 rakstzīmes"</string>
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Izmantojiet mazāk nekā <xliff:g id="LENGTH">%1$d</xliff:g> rakstzīmes."</string>
- <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijas numurs"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Būvējuma numurs"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Versijas numurs ir kopēts starpliktuvē."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopēt starpliktuvē."</string>
<string name="basic_status" msgid="2315371112182658176">"Atvērt sarunu"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Sarunu logrīki"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Pieskarieties kādai sarunai, lai pievienotu to savam sākuma ekrānam."</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Lai izmantotu augstāku izšķirtspēju, apvērsiet tālruni"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Priekšējais ekrāns ir ieslēgts"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"aizvērta"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"atvērta"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistēmas vadīklas"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistēmas lietotnes"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Vairākuzdevumu režīms"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Pēdējās izmantotās lietotnes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ekrāna sadalīšana"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Ievade"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Lietotņu saīsnes"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pieejamība"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Īsinājumtaustiņi"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Īsinājumtaustiņu pielāgošana"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Lai piešķirtu īsinājumtaustiņu, nospiediet taustiņu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vai noņemt saīsni?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Lai piešķirtu īsinājumtaustiņu, nospiediet taustiņu"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Veicot šo darbību, jūsu pielāgotā saīsne tiks neatgriezeniski izdzēsta."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Meklēt saīsnes"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nav meklēšanas rezultātu"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sakļaušanas ikona"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Darbību jeb meta taustiņa ikona"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pluszīmes ikona"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Pielāgot"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gatavs"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Izvēršanas ikona"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vai"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"uz priekšu vērstā slīpsvītra"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkšanas turis"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastatūras iestatījumi"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Iestatīt īsinājumtaustiņu"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Noņemt"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Atcelt"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nospiediet taustiņu"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Taustiņu kombinācija jau tiek izmantota. Izmēģiniet citu taustiņu."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Taustiņu kombinācija jau tiek izmantota. Izmēģiniet citu taustiņu."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nevar iestatīt saīsni."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Pārvietošanās, izmantojot tastatūru"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Uzziniet par īsinājumtaustiņiem."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Pārvietošanās, izmantojot skārienpaliktni"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index f8bfcf9296ca..f991c1b68aac 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Поврзано"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Споделување аудио"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Допрете за да префрлите или споделите аудио"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддржува споделување аудио"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Зачувано"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виџети на заклучен екран"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"За да отворите апликација со помош на виџет, ќе треба да потврдите дека сте вие. Покрај тоа, имајте предвид дека секој може да ги гледа виџетите, дури и кога вашиот таблет е заклучен. Некои виџети можеби не се наменети за вашиот заклучен екран, па можеби не е безбедно да се додадат овде."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Сфатив"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виџети"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијава ќе се избришат."</string>
@@ -698,7 +703,7 @@
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола на шум"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Просторен звук"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Исклучено"</string>
- <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Фиксно"</string>
+ <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Фиксирано"</string>
<string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Следење на главата"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Допрете за да го промените режимот на ѕвончето"</string>
<string name="volume_ringer_mode" msgid="6867838048430807128">"режим на ѕвонче"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Се прикажува најгоре во известувањата за разговор и како профилна слика на заклучен екран, се појавува како балонче, го прекинува „Не вознемирувај“"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритетно"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддржува функции за разговор"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Испрати повратни информации за пакет"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Овие известувања не може да се изменат"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Известувањата за повици не може да се изменат."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Оваа група известувања не може да се конфигурира тука"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Префрлете се на апликацијата лево или горе при користењето поделен екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"При поделен екран: префрлете ги аплик. од едната на другата страна"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Движете го активниот прозорец меѓу екраните"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Премести го прозорецот налево"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Премести го прозорецот надесно"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Максимизирај го прозорецот"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Минимизирај го прозорецот"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Внесување"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Префрлете на следниот јазик"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Префрлете на претходниот јазик"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Употребете помалку од <xliff:g id="LENGTH">%1$d</xliff:g> знаци"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Број на верзија"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Бројот на верзијата е копиран во привремената меморија."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копирање во привремената меморија."</string>
<string name="basic_status" msgid="2315371112182658176">"Започни разговор"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Виџети за разговор"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Допрете разговор за да го додадете на почетниот екран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Отворете го телефонот за да добиете повисока резолуција"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предниот екран е вклучен"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворен"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворен"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Системски контроли"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системски апликации"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Мултитаскинг"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Неодамнешни апликации"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Поделен екран"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Внесување"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Кратенки за апликации"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Пристапност"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Кратенки од тастатура"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Приспособете ги кратенките од тастатурата"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Притиснете го копчето за да доделите кратенка"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се отстрани кратенката?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притиснете го копчето за да доделите кратенка"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ова ќе ја избрише вашата приспособена кратенка трајно."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пребарувајте кратенки"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултати од пребарување"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за собирање"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Икона за дејство или копче за дејство"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Икона плус"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Приспособете"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширување"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"плус"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"коса црта"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Рачка за влечење"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Поставки за тастатурата"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Поставете кратенка"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Отстрани"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Откажи"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Притиснете го копчето"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбинацијата на копчиња веќе се користи. Обидете се со друго копче."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинацијата на копчиња веќе се користи. Обидете се со друго копче."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Кратенката не може да се постави."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Движете се со користење на тастатурата"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете ги кратенките од тастатурата"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Движете се со користење на допирната подлога"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 54364c804581..784c896771d1 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ഉപയോഗിക്കുക"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"കണക്‌റ്റ് ചെയ്‌തു"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ഓഡിയോ പങ്കിടൽ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ഓഡിയോ മാറാനോ പങ്കിടാനോ ടാപ്പ് ചെയ്യുക"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ഓഡിയോ പങ്കിടൽ പിന്തുണയ്ക്കുന്നു"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"സംരക്ഷിച്ചു"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ലോക്ക് സ്‌ക്രീൻ വിജറ്റുകൾ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"വിജറ്റ് ഉപയോഗിച്ച് ഒരു ആപ്പ് തുറക്കാൻ, ഇത് നിങ്ങൾ തന്നെയാണെന്ന് പരിശോധിച്ചുറപ്പിക്കേണ്ടതുണ്ട്. നിങ്ങളുടെ ടാബ്‌ലെറ്റ് ലോക്കായിരിക്കുമ്പോഴും എല്ലാവർക്കും അത് കാണാനാകുമെന്നതും ഓർക്കുക. ചില വിജറ്റുകൾ നിങ്ങളുടെ ലോക്ക് സ്‌ക്രീനിന് ഉള്ളതായിരിക്കില്ല, അവ ഇവിടെ ചേർക്കുന്നത് സുരക്ഷിതവുമായിരിക്കില്ല."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"മനസ്സിലായി"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"വിജറ്റുകൾ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"സംഭാഷണ അറിയിപ്പുകളുടെ മുകളിലും സ്ക്രീൻ ലോക്കായിരിക്കുമ്പോൾ ഒരു പ്രൊഫൈൽ ചിത്രമായും ബബിൾ രൂപത്തിൽ ദൃശ്യമാകുന്നു, ശല്യപ്പെടുത്തരുത് മോഡ് തടസ്സപ്പെടുത്തുന്നു"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"മുൻഗണന"</string>
<string name="no_shortcut" msgid="8257177117568230126">"സംഭാഷണ ഫീച്ചറുകളെ <xliff:g id="APP_NAME">%1$s</xliff:g> പിന്തുണയ്‌ക്കുന്നില്ല"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ബണ്ടിൽ ഫീഡ്ബാക്ക് നൽകുക"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ഈ അറിയിപ്പുകൾ പരിഷ്ക്കരിക്കാനാവില്ല."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"കോൾ അറിയിപ്പുകൾ പരിഷ്‌കരിക്കാനാകുന്നില്ല."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"അറിയിപ്പുകളുടെ ഈ ഗ്രൂപ്പ് ഇവിടെ കോണ്‍ഫിഗര്‍ ചെയ്യാൻ കഴിയില്ല"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"സ്ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുമ്പോൾ ഇടതുവശത്തെ/മുകളിലെ ആപ്പിലേക്ക് മാറൂ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"സ്‌ക്രീൻ വിഭജന മോഡിൽ: ഒരു ആപ്പിൽ നിന്ന് മറ്റൊന്നിലേക്ക് മാറുക"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"സജീവ വിൻഡോകൾ ഡിസ്‌പ്ലേകൾക്ക് ഇടയിൽ നീക്കുക"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"വിൻഡോ ഇടത്തേക്ക് നീക്കുക"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"വിൻഡോ വലത്തേക്ക് നീക്കുക"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"വിൻഡോ വലുതാക്കുക"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"വിൻഡോ ചെറുതാക്കുക"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ഇൻപുട്ട്"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"അടുത്ത ഭാഷയിലേക്ക് മാറുക"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"മുമ്പത്തെ ഭാഷയിലേക്ക് മാറുക"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>-ൽ കുറവ് പ്രതീകങ്ങൾ ഉപയോഗിക്കുക"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ബിൽഡ് നമ്പർ"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ക്ലിപ്പ്ബോർഡിലേക്ക് ബിൽഡ് നമ്പർ പകർത്തി."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക."</string>
<string name="basic_status" msgid="2315371112182658176">"സംഭാഷണം തുറക്കുക"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"സംഭാഷണ വിജറ്റുകൾ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"നിങ്ങളുടെ ഹോം സ്‌ക്രീനിൽ ചേർക്കാൻ സംഭാഷണത്തിൽ ടാപ്പ് ചെയ്യുക"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ഉയർന്ന റെസല്യൂഷന്, ഫോൺ ഫ്ലിപ്പ് ചെയ്യുക"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ഫ്രണ്ട് സ്ക്രീൻ ഓണാക്കി"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ഫോൾഡ് ചെയ്തത്"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"അൺഫോൾഡ് ചെയ്തത്"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"സിസ്‌റ്റം നിയന്ത്രണങ്ങൾ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"സിസ്‌റ്റം ആപ്പുകൾ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"മൾട്ടിടാസ്‌കിംഗ്"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"അടുത്തിടെ ഉപയോഗിച്ച ആപ്പുകൾ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"സ്‌ക്രീൻ വിഭജന മോഡ്"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ഇൻപുട്ട്"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ആപ്പ് കുറുക്കുവഴികൾ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ഉപയോഗസഹായി"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"കീബോഡ് കുറുക്കുവഴികൾ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"കീബോർഡ് കുറുക്കുവഴികൾ ഇഷ്ടാനുസൃതമാക്കുക"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"കുറുക്കുവഴി അസൈൻ ചെയ്യാൻ കീ അമർത്തുക"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"കുറുക്കുവഴി നീക്കം ചെയ്യണോ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"കുറുക്കുവഴി അസൈൻ ചെയ്യാൻ കീ അമർത്തുക"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ഇത് നിങ്ങളുടെ ഇഷ്‌ടാനുസൃത കുറുക്കുവഴി ശാശ്വതമായി ഇല്ലാതാക്കും."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"തിരയൽ കുറുക്കുവഴികൾ"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"തിരയൽ ഫലങ്ങളൊന്നുമില്ല"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ചുരുക്കൽ ഐക്കൺ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ആക്ഷൻ/മെറ്റാ കീ ഐക്കൺ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"പ്ലസ് ഐക്കൺ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"പൂർത്തിയായി"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"വികസിപ്പിക്കൽ ഐക്കൺ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"അല്ലെങ്കിൽ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"പ്ലസ്"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ഫോർവേഡ് സ്ലാഷ്"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"കീബോർഡ് ക്രമീകരണം"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"കുറുക്കുവഴി സജ്ജീകരിക്കുക"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"നീക്കം ചെയ്യുക"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"റദ്ദാക്കുക"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"കീ അമർത്തുക"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കീ പരീക്ഷിക്കുക."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കീ പരീക്ഷിക്കുക."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"കുറുക്കുവഴി സജ്ജീകരിക്കാനാകില്ല."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"നിങ്ങളുടെ കീബോർഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"കീബോർഡ് കുറുക്കുവഴികൾ മനസ്സിലാക്കുക"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"നിങ്ങളുടെ ടച്ച്‌പാഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 0e2dc23e88c1..6e60b718a6a1 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-г ашиглах"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Холбогдсон"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Аудио хуваалцах"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудиог сэлгэх эсвэл хуваалцахын тулд товшино уу"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Аудио хуваалцахыг дэмждэг"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Хадгалсан"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Түгжээтэй дэлгэцийн виджет"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Виджет ашиглан аппыг нээхийн тулд та өөрийгөө мөн болохыг баталгаажуулах шаардлагатай болно. Мөн таны таблет түгжээтэй байсан ч тэдгээрийг дурын хүн үзэж болохыг санаарай. Зарим виджет таны түгжээтэй дэлгэцэд зориулагдаагүй байж магадгүй ба энд нэмэхэд аюултай байж болзошгүй."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ойлголоо"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджет"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Харилцан ярианы мэдэгдлийн дээд талд болон түгжигдсэн дэлгэц дээр профайл зураг байдлаар харуулах бөгөөд бөмбөлөг хэлбэрээр харагдана. Бүү саад бол горимыг тасалдуулна"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Чухал"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> нь харилцан ярианы онцлогуудыг дэмждэггүй"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Багц санал хүсэлт өгөх"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Эдгээр мэдэгдлийг өөрчлөх боломжгүй."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Дуудлагын мэдэгдлийг өөрчлөх боломжгүй."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Энэ бүлэг мэдэгдлийг энд тохируулах боломжгүй байна"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Дэлгэц хуваахыг ашиглаж байхдаа зүүн талд эсвэл дээр байх апп руу сэлгэ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Дэлгэц хуваах үеэр: аппыг нэгээс нөгөөгөөр солих"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Идэвхтэй цонхыг дэлгэц хооронд зөөх"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Цонхыг зүүн тийш зөөх"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Цонхыг баруун тийш зөөх"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Цонхыг томруулах"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Цонхыг багасгах"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Оролт"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Дараагийн хэл рүү сэлгэх"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Өмнөх хэл рүү сэлгэх"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>-с цөөн тэмдэгт ашиглана уу"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Хийцийн дугаар"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Хийцийн дугаарыг түр санах ойд хуулсан."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"түр санах ойд хуулна уу."</string>
<string name="basic_status" msgid="2315371112182658176">"Харилцан яриаг нээх"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Харилцан ярианы виджетүүд"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Үндсэн нүүрэндээ нэмэх харилцан яриаг товшино уу"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Илүү өндөр нягтрал авах бол утсыг хөнтөрнө үү"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Нүүрэн талын дэлгэцийг асаасан"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"эвхсэн"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"дэлгэсэн"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Системийн тохиргоо"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системийн аппууд"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Олон ажил зэрэг хийх"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Саяхны аппууд"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Дэлгэцийг хуваах"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Оролт"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Аппын товчлол"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Хандалт"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Товчлуурын шууд холбоос"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Товчлуурын шууд холбоосыг өөрчлөх"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Товчлол оноохын тулд товч дарна уу"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Товчлолыг хасах уу?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Товчлол оноохын тулд товч дарна уу"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Энэ нь таны захиалгат товчлолыг бүрмөсөн устгана."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Товчлолууд хайх"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ямар ч хайлтын илэрц байхгүй"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Хураах дүрс тэмдэг"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Үйлдлийн товч буюу өөрөөр Мета товчийн дүрс тэмдэг"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Нэмэх дүрс тэмдэг"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Өөрчлөх"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Болсон"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Дэлгэх дүрс тэмдэг"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"эсвэл"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"нэмэх нь"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"урагшаа ташуу зураас"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Чирэх бариул"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Гарын тохиргоо"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Товчлол тохируулах"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Хасах"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Цуцлах"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Товч дарна уу"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр товч туршиж үзнэ үү."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр товч туршиж үзнэ үү."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Товчлол тохируулах боломжгүй."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Гараа ашиглан шилжих"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Товчлуурын шууд холбоосыг мэдэж аваарай"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Мэдрэгч самбараа ашиглан шилжээрэй"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index cd9062252425..c21a666026af 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्‍लूटूथ वापरा"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट केले"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ऑडिओ शेअरिंग"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"व्हिडिओवर स्विच करण्यासाठी टॅप करा किंवा शेअर करा"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ऑडिओ शेअरिंगला सपोर्ट करते"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव्ह केले"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लॉक स्‍क्रीन विजेट"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"विजेट वापरून अ‍ॅप उघडण्यासाठी, तुम्हाला हे तुम्हीच असल्याची पडताळणी करावी लागेल. तसेच, लक्षात ठेवा, तुमचा टॅबलेट लॉक असतानादेखील कोणीही ती पाहू शकते. काही विजेट कदाचित तुमच्या लॉक स्‍क्रीनसाठी नाहीत आणि ती इथे जोडणे असुरक्षित असू शकते."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"समजले"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेट"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अ‍ॅप्स आणि डेटा हटवला जाईल."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"संभाषण सूचनांच्या वरती आणि लॉक स्क्रीनवरील प्रोफाइल फोटो म्हणून दिसते, बबल म्हणून दिसते, व्यत्यय आणू नका यामध्ये अडथळा आणते"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"प्राधान्य"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे संभाषण वैशिष्ट्यांना सपोर्ट करत नाही"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बंडलसंबंधित फीडबॅक द्या"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"या सूचनांमध्ये सुधारणा केली जाऊ शकत नाही."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कॉलशी संबंधित सूचनांमध्ये फेरबदल केला जाऊ शकत नाही."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"या सूचनांचा संच येथे कॉन्फिगर केला जाऊ शकत नाही"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन वापरताना डावीकडील किंवा वरील अ‍ॅपवर स्विच करा"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीनदरम्यान: एक अ‍ॅप दुसऱ्या अ‍ॅपने बदला"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ॲक्टिव्ह विंडो डिस्प्लेदरम्यान हलवा"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"विंडो डावीकडे हलवा"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"विंडो उजवीकडे हलवा"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"विंडो मोठी करा"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"विंडो लहान करा"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"पुढील भाषेवर स्विच करा"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"मागील भाषेवर स्विच करा"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वर्णांपेक्षा कमी वर्ण वापरा"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर क्लिपबोर्डवर कॉपी केला."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"क्लिपबोर्डवर कॉपी करा."</string>
<string name="basic_status" msgid="2315371112182658176">"संभाषण उघडा"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"संभाषण विजेट"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"तुमच्या होम स्क्रीन वर संभाषण जोडण्यासाठी त्यावर टॅप करा"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रेझोल्यूशनसाठी, फोन फ्लिप करा"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"पुढील स्क्रीन सुरू केलेली आहे"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"फोल्ड केलेले"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"फोल्ड न केलेले"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"सिस्‍टीमची नियंत्रणे"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टीम अ‍ॅप्स"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"मल्टिटास्किंग"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"अलीकडील अ‍ॅप्स"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रीन"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"इनपुट"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"अ‍ॅप शॉर्टकट"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"अ‍ॅक्सेसिबिलिटी"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"कीबोर्ड शॉर्टकट कस्टमाइझ करा"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"शॉर्टकट असाइन करण्यासाठी की प्रेस करा"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"शॉर्टकट काढून टाकायचा आहे का?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करण्यासाठी की प्रेस करा"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यामुळे तुमचा कस्टम शॉर्टकट कायमचा हटवला जाईल."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शोधण्यासाठी शॉर्टकट"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कोणतेही शोध परिणाम नाहीत"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"कोलॅप्स करा आयकन"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"कृती किंवा मेटा की आयकन"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"अधिक आयकन"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"कस्टमाइझ करा"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"पूर्ण झाले"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"विस्तार करा आयकन"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"किंवा"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"अधिक"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"फॉरवर्ड स्लॅश"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्रॅग हॅंडल"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"कीबोर्ड सेटिंग्ज"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"शॉर्टकट सेट करा"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"काढून टाका"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द करा"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"की प्रेस करा"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरी की वापरून पहा."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरी की वापरून पहा."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट करू शकत नाही."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"तुमचा कीबोर्ड वापरून नेव्हिगेट करा"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"कीबोर्ड शॉर्टकट जाणून घ्या"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"तुमचा टचपॅड वापरून नेव्हिगेट करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index c108fc476da0..62f85a5e9fb2 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Perkongsian Audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ketik untuk menukar atau berkongsi audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Menyokong perkongsian audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget skrin kunci"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Untuk membuka apl menggunakan widget, anda perlu mengesahkan identiti anda. Selain itu, perlu diingat bahawa sesiapa sahaja boleh melihat widget tersebut, walaupun semasa tablet anda dikunci. Sesetengah widget mungkin tidak sesuai untuk skrin kunci anda dan mungkin tidak selamat untuk ditambahkan di sini."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ditunjukkan di bahagian atas pemberitahuan perbualan dan sebagai gambar profil pada skrin kunci, muncul sebagai gelembung, mengganggu Jangan Ganggu"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Keutamaan"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak menyokong ciri perbualan"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Berikan Maklum Balas Himpunan"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Pemberitahuan ini tidak boleh diubah suai."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Pemberitahuan panggilan tidak boleh diubah suai."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Kumpulan pemberitahuan ini tidak boleh dikonfigurasikan di sini"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Tukar kepada apl di sebelah kiri/atas semasa menggunakan skrin pisah"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Semasa skrin pisah: gantikan apl daripada satu apl kepada apl lain"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Alihkan tetingkap aktif antara paparan"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Alihkan tetingkap ke sebelah kiri"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Alihkan tetingkap ke sebelah kanan"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimumkan tetingkap"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimumkan tetingkap"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Beralih kepada bahasa seterusnya"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Beralih kepada bahasa sebelumnya"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gunakan kurang daripada <xliff:g id="LENGTH">%1$d</xliff:g> aksara"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nombor binaan"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Nombor binaan disalin ke papan keratan."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"salin kepada papan keratan."</string>
<string name="basic_status" msgid="2315371112182658176">"Buka perbualan"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widget perbualan"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Ketik perbualan untuk menambahkan perbualan itu pada skrin Utama anda"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk peleraian lebih tinggi, balikkan telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Skrin hadapan dihidupkan"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"terlipat"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tidak terlipat"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Kawalan sistem"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apl sistem"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Berbilang tugas"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Apl terbaharu"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Skrin pisah"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Pintasan apl"</string>
@@ -1418,27 +1425,42 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kebolehaksesan"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan papan kekunci"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sesuaikan pintasan papan kekunci"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tekan kekunci untuk menetapkan pintasan"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alih keluar pintasan?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan kekunci untuk menetapkan pintasan"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan memadamkan pintasan tersuai anda secara kekal."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan carian"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tiada hasil carian"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kuncupkan ikon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikon kekunci tindakan atau Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikon tambah"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Sesuaikan"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Selesai"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kembangkan ikon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"tambah"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"garis condong ke hadapan"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Pemegang seret"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tetapan Papan Kekunci"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tetapkan pintasan"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Alih keluar"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Batal"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tekan kekunci"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Gabungan kekunci sudah digunakan. Cuba kekunci lain."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Gabungan kekunci sudah digunakan. Cuba kekunci lain."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pintasan tidak boleh ditetapkan."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigasi menggunakan papan kekunci"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ketahui pintasan papan kekunci"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigasi menggunakan pad sentuh anda"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Ketahui gerak isyarat pad sentuh"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Navigasi menggunakan papan kekunci dan pad sentuh anda"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Navigasi menggunakan papan kekunci dan pad sentuh"</string>
<string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Ketahui gerak isyarat pad sentuh, pintasan papan kekunci dan pelbagai lagi"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Kembali"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Akses laman utama"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index ce52f4e4e024..393f2f2dd46b 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ဘလူးတုသ်သုံးရန်"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ချိတ်ဆက်ထားသည်"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"အော်ဒီယို မျှဝေခြင်း"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"အသံ ပြောင်းရန်/မျှဝေရန် တို့ပါ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
@@ -454,7 +454,7 @@
<string name="zen_mode_off" msgid="1736604456618147306">"ပိတ်"</string>
<string name="zen_mode_set_up" msgid="8231201163894922821">"သတ်မှတ်မထားပါ"</string>
<string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ဆက်တင်များတွင် စီမံရန်"</string>
- <string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{သုံးနေသော မုဒ်မရှိပါ}=1{{mode} ကို သုံးနေသည်}other{မုဒ် # ခု သုံးနေသည်}}"</string>
+ <string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{မုဒ် သုံးမနေပါ}=1{{mode} ကို သုံးနေသည်}other{မုဒ် # ခု သုံးနေသည်}}"</string>
<string name="zen_priority_introduction" msgid="3159291973383796646">"နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
<string name="zen_alarms_introduction" msgid="3987266042682300470">"နှိုးစက်သံမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
<string name="zen_priority_customize_button" msgid="4119213187257195047">"စိတ်ကြိုက် ပြုလုပ်ရန်"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"လော့ခ်မျက်နှာပြင် ဝိဂျက်များ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ဝိဂျက်သုံး၍ အက်ပ်ဖွင့်ရန်အတွက် သင်ဖြစ်ကြောင်း အတည်ပြုရန်လိုသည်။ ထို့ပြင် သင့်တက်ဘလက် လော့ခ်ချထားချိန်၌ပင် မည်သူမဆို ၎င်းတို့ကို ကြည့်နိုင်ကြောင်း သတိပြုပါ။ ဝိဂျက်အချို့ကို လော့ခ်မျက်နှာပြင်အတွက် ရည်ရွယ်ထားခြင်း မရှိသဖြင့် ဤနေရာတွင် ထည့်ပါက မလုံခြုံနိုင်ပါ။"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"နားလည်ပြီ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ဝိဂျက်များ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"စကားဝိုင်း အကြောင်းကြားချက်များ၏ ထိပ်ပိုင်းနှင့် ပရိုဖိုင်ပုံအဖြစ် လော့ခ်မျက်နှာပြင်တွင် ပြသည်။ ပူဖောင်းကွက်အဖြစ် မြင်ရပြီး ‘မနှောင့်ယှက်ရ’ ကို ကြားဖြတ်သည်"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ဦးစားပေး"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> က စကားဝိုင်းဝန်ဆောင်မှုများကို မပံ့ပိုးပါ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"အတွဲလိုက် အကြံပြုချက်ပေးရန်"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ဤအကြောင်းကြားချက်များကို ပြုပြင်၍ မရပါ။"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ခေါ်ဆိုမှုအကြောင်းကြားချက်များကို ပြင်၍မရပါ။"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ဤအကြောင်းကြားချက်အုပ်စုကို ဤနေရာတွင် စီစဉ်သတ်မှတ်၍ မရပါ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသုံးစဉ် ဘယ် (သို့) အထက်ရှိအက်ပ်သို့ ပြောင်းရန်"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"မျက်နှာပြင် ခွဲ၍ပြသစဉ်- အက်ပ်တစ်ခုကို နောက်တစ်ခုနှင့် အစားထိုးရန်"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"လက်ရှိဝင်းဒိုးကို ပြကွက်များအကြား ရွှေ့ခြင်း"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ဝင်းဒိုးကို ဘယ်ဘက်ရွှေ့ရန်"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ဝင်းဒိုးကို ညာဘက်ရွှေ့ရန်"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ဝင်ဒိုးကို ချဲ့ရန်"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ဝင်းဒိုးကို ချုံ့ရန်"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"စာရိုက်ခြင်း"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"နောက်ဘာသာစကားသို့ ပြောင်းရန်"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ယခင်ဘာသာစကားသို့ ပြောင်းရန်"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"အက္ခရာ <xliff:g id="LENGTH">%1$d</xliff:g> လုံးအောက် သုံးရန်"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"တည်ဆောက်ပုံအမှတ်"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"တည်ဆောက်မှုနံပါတ်ကို ကလစ်ဘုတ်သို့ မိတ္တူကူးပြီးပါပြီ။"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ကလစ်ဘုတ်သို့ မိတ္တူကူးရန်။"</string>
<string name="basic_status" msgid="2315371112182658176">"စကားဝိုင်းကို ဖွင့်ရန်"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"စကားဝိုင်း ဝိဂျက်များ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"စကားဝိုင်းကို သင်၏ ‘ပင်မစာမျက်နှာ’ သို့ထည့်ရန် တို့ပါ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ပုံရိပ် ပိုမိုပြတ်သားစေရန် ဖုန်းကို တစ်ဖက်သို့ လှန်လိုက်ပါ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ရှေ့စခရင် ဖွင့်ထားသည်"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ခေါက်ထားသည်"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ဖြန့်ထားသည်"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"စနစ် ထိန်းချုပ်မှုများ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"စနစ် အက်ပ်များ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"တစ်ပြိုင်နက် များစွာလုပ်ခြင်း"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"မကြာသေးမီက အက်ပ်များ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ထည့်သွင်းမှု"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"အက်ပ်ဖြတ်လမ်းလင့်ခ်များ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"အများသုံးနိုင်မှု"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"လက်ကွက်ဖြတ်လမ်းများ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"လက်ကွက်ဖြတ်လမ်းများကို စိတ်ကြိုက်လုပ်ခြင်း"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ဖြတ်လမ်းသတ်မှတ်ရန် ကီးကို နှိပ်ပါ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ဖြတ်လမ်းလင့်ခ် ဖယ်ရှားမလား။"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ဖြတ်လမ်းလင့်ခ်သတ်မှတ်ရန် ကီးကို နှိပ်ပါ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"၎င်းသည် သင့်စိတ်ကြိုက် ဖြတ်လမ်းလင့်ခ်ကို အပြီးဖျက်ပါမည်။"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ဖြတ်လမ်းများ ရှာရန်"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ရှာဖွေမှုရလဒ် မရှိပါ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"လျှော့ပြရန် သင်္ကေတ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"လုပ်ဆောင်ချက် (သို့) Meta ကီးသင်္ကေတ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"အပေါင်းသင်္ကေတ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"စိတ်ကြိုက်လုပ်ရန်"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ပြီးပြီ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ပိုပြရန် သင်္ကေတ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"သို့မဟုတ်"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"အပေါင်း"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ညာဘက်မျဉ်းစောင်း"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ဖိဆွဲအထိန်း"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ကီးဘုတ်ဆက်တင်များ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ဖြတ်လမ်း သတ်မှတ်ရန်"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ဖယ်ရှားရန်"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"မလုပ်တော့"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ကီးကို နှိပ်ပါ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားကီးကို စမ်းကြည့်ပါ။"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားကီးကို စမ်းကြည့်ပါ။"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ဖြတ်လမ်းလင့်ခ် သတ်မှတ်၍မရပါ။"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"သင့်ကီးဘုတ်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"လက်ကွက်ဖြတ်လမ်းများကို လေ့လာပါ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"သင့်တာ့ချ်ပက်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index db352a5fc4b0..7454d632ee3a 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Trykk for å bytte eller dele lyd"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Støtter lyddeling"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Låseskjermmoduler"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"For å åpne en app ved hjelp av en modul må du bekrefte at det er deg. Husk også at hvem som helst kan se dem, selv om nettbrettet er låst. Noen moduler er kanskje ikke laget for å være på låseskjermen og kan være utrygge å legge til der."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Greit"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Moduler"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Vises øverst på samtalevarsler og som et profilbilde på låseskjermen, vises som en boble, avbryter «Ikke forstyrr»"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> støtter ikke samtalefunksjoner"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Gi tilbakemelding om pakken"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Disse varslene kan ikke endres."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Anropsvarsler kan ikke endres."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Denne varselgruppen kan ikke konfigureres her"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bytt til appen til venstre eller over mens du bruker delt skjerm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"I delt skjerm: Bytt ut en app"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flytt det aktive vinduet mellom skjermer"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Flytt vinduet til venstre"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Flytt vinduet til høyre"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimer vinduet"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimerer vinduet"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Skrivespråk"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Bytt til neste språk"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Bytt til forrige språk"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Bruk færre enn <xliff:g id="LENGTH">%1$d</xliff:g> tegn"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Delversjonsnummer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Delversjonsnummeret er kopiert til utklippstavlen."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Kopier til utklippstavlen."</string>
<string name="basic_status" msgid="2315371112182658176">"Åpen samtale"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Samtalemoduler"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Trykk på en samtale for å legge den til på startskjermen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Brett ut telefonen for å få høyere oppløsning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En foldbar enhet blir brettet ut"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En foldbar enhet blir snudd"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontskjermen er slått på"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"lagt sammen"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"åpen"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Systemkontroller"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systemapper"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nylige apper"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Delt skjerm"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Inndata"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App-snarveier"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tilgjengelighet"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Hurtigtaster"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tilpass hurtigtastene"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Trykk på tasten for å tilordne hurtigtasten"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vil du fjerne hurtigtasten?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Trykk på en tast for å tilordne hurtigtasten"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dette fører til at den egendefinerte hurtigtasten slettes permanent."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snarveier til søk"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ingen søkeresultater"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Skjul-ikon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Handlings- eller Meta-tast-ikon"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plussikon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Tilpass"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Ferdig"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vis-ikon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"pluss"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"skråstrek"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtak"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastaturinnstillinger"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Angi hurtigtast"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjern"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Avbryt"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Trykk på tasten"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tastekombinasjonen brukes allerede. Prøv en annen tast."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinasjonen brukes allerede. Prøv en annen tast."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kan ikke angi snarveien."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger med tastaturet"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lær deg hurtigtaster"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger med styreflaten"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 2c1d7271ac19..9b8af93ab0d6 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लुटुथ प्रयोग गर्नुहोस्"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट गरिएको छ"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"अडियो सेयरिङ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"अडियो बदल्न वा सेयर गर्न ट्याप गर्नुहोस्"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"अडियो सेयर गर्न मिल्छ"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेभ गरिएको छ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लक स्क्रिन विजेटहरू"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"विजेट प्रयोग गरी एप खोल्न तपाईंले आफ्नो पहिचान पुष्टि गर्नु पर्ने हुन्छ। साथै, तपाईंको ट्याब्लेट लक भएका बेला पनि सबै जनाले तिनलाई देख्न सक्छन् भन्ने कुरा ख्याल गर्नुहोस्। केही विजेटहरू लक स्क्रिनमा प्रयोग गर्ने उद्देश्यले नबनाइएका हुन सक्छन् र तिनलाई यहाँ हाल्नु सुरक्षित नहुन सक्छ।"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"बुझेँ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेटहरू"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"यो वार्तालापका सूचनाहरूको सिरानमा, बबलका रूपमा र लक स्क्रिनमा प्रोफाइल फोटोका रूपमा देखिन्छ। साथै, यसले गर्दा \'बाधा नपुऱ्याउनुहोस्\' नामक सुविधामा अवरोध आउँछ"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"प्राथमिकता"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा वार्तालापसम्बन्धी सुविधा प्रयोग गर्न मिल्दैन"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बन्डलका बारेमा प्रतिक्रिया दिनुहोस्"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"यी सूचनाहरू परिमार्जन गर्न मिल्दैन।"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कलसम्बन्धी सूचनाहरू परिमार्जन गर्न मिल्दैन।"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"यहाँबाट सूचनाहरूको यो समूह कन्फिगर गर्न सकिँदैन"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रिन प्रयोग गर्दै गर्दा बायाँ वा माथिको एप चलाउनुहोस्"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रिन प्रयोग गरिएका बेला: एउटा स्क्रिनमा भएको एप अर्कोमा लैजानुहोस्"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"सक्रिय विन्डोलाई एउटा डिस्प्लेबाट सारेर अर्को डिस्प्लेमा लैजानुहोस्"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"विन्डो सारेर बायाँतिर लैजानुहोस्"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"विन्डो सारेर दायाँतिर लैजानुहोस्"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"विन्डो म्याक्सिमाइज गर्नुहोस्"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"विन्डो मिनिमाइज गर्नुहोस्"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"अर्को भाषा प्रयोग गर्नुहोस्"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"अघिल्लो भाषा प्रयोग गर्नुहोस्"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वटा भन्दा कम वर्ण प्रयोग गर्नुहोस्"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नम्बर"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नम्बर कपी गरी क्लिपबोर्डमा सारियो।"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"कपी गरेर क्लिपबोर्डमा पेस्ट गर्नुहोस्।"</string>
<string name="basic_status" msgid="2315371112182658176">"वार्तालाप खोल्नुहोस्"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"वार्तालापसम्बन्धी विजेटहरू"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"कुनै वार्तालाप होम स्क्रिनमा हाल्न उक्त वार्तालापमा ट्याप गर्नुहोस्"</string>
@@ -1355,10 +1364,9 @@
<string name="rear_display_unfolded_bottom_sheet_title" msgid="6291111173057304055">"स्क्रिनहरू बदल्ने हो?"</string>
<string name="rear_display_folded_bottom_sheet_description" msgid="6842767125783222695">"उच्च रिजोल्युसनको सेल्फी खिच्न पछाडिको क्यामेरा प्रयोग गर्नुहोस्"</string>
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रिजोल्युसनको सेल्फी खिच्न फोन फ्लिप गर्नुहोस्"</string>
- <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
- <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्डेबल डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
+ <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्डेबल डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"अगाडिको स्क्रिन अन गरिएको छ"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"फोल्ड गरिएको"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"अनफोल्ड गरिएको"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"सिस्टमसँग सम्बन्धित नियन्त्रणहरू"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टम एपहरू"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"एकै पटक एकभन्दा बढी एप चलाउन मिल्ने सुविधा"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"हालसालै चलाइएका एपहरू"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रिन"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"इनपुट"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"एपका सर्टकटहरू"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सर्वसुलभता"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"किबोर्डका सर्टकटहरू"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"किबोर्डका सर्टकटहरू कस्टमाइज गर्नुहोस्"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"सर्टकट असाइन गर्न की थिच्नुहोस्"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"सर्टकट हटाउने हो?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"सर्टकट असाइन गर्न की थिच्नुहोस्"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यसो गर्नुभयो भने तपाईंको कस्टम सर्टकट सदाका लागि मेटिने छ।"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"खोजका सर्टकटहरू"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कुनै पनि खोज परिणाम भेटिएन"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\"कोल्याप्स गर्नुहोस्\" आइकन"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"एक्सन वा Meta कीको आइकन"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"प्लस आइकन"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"कस्टमाइज गर्नुहोस्"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"पूरा भयो"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\"एक्स्पान्ड गर्नुहोस्\" आइकन"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"वा"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"प्लस"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"फर्वार्ड स्ल्यास"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्र्याग ह्यान्डल"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"किबोर्डसम्बन्धी सेटिङ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"सर्टकट सेट गर्नुहोस्"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"हटाउनुहोस्"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द गर्नुहोस्"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"की थिच्नुहोस्"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै की प्रयोग गरी हेर्नुहोस्।"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै की प्रयोग गरी हेर्नुहोस्।"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"सर्टकट सेट गर्न सकिएन।"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"किबोर्ड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"किबोर्डका सर्टकटहरू प्रयोग गर्न सिक्नुहोस्"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"टचप्याड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 0d195a3b40d7..12f415609065 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio delen"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tik om audio te schakelen of te delen"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ondersteunt audio delen"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets op het vergrendelscherm"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Als je een app wilt openen met een widget, moet je verifiëren dat jij het bent. Houd er ook rekening mee dat iedereen ze kan bekijken, ook als je tablet vergrendeld is. Bepaalde widgets zijn misschien niet bedoeld voor je vergrendelscherm en kunnen hier niet veilig worden toegevoegd."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wordt getoond bovenaan gespreksmeldingen en als profielfoto op het vergrendelscherm, verschijnt als bubbel, onderbreekt Niet storen"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ondersteunt geen gespreksfuncties"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Feedback over bundel geven"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Deze meldingen kunnen niet worden aangepast."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Gespreksmeldingen kunnen niet worden aangepast."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Deze groep meldingen kan hier niet worden ingesteld"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Naar de app links of bovenaan gaan als je een gesplitst scherm gebruikt"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Tijdens gesplitst scherm: een app vervangen door een andere"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Actief venster verplaatsen tussen schermen"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Venster naar links verplaatsen"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Venster naar rechts verplaatsen"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Venster maximaliseren"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Venster minimaliseren"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Invoer"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Overschakelen naar volgende taal"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Overschakelen naar vorige taal"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gebruik minder dan <xliff:g id="LENGTH">%1$d</xliff:g> tekens"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Buildnummer naar klembord gekopieerd."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiëren naar klembord."</string>
<string name="basic_status" msgid="2315371112182658176">"Gesprek openen"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Gesprekswidgets"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tik op een gesprek om het toe te voegen aan je startscherm"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Draai de telefoon om voor een hogere resolutie"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Scherm aan voorzijde aangezet"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dichtgevouwen"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opengevouwen"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Systeemopties"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systeem-apps"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Recente apps"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Gesplitst scherm"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Invoer"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App-sneltoetsen"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toegankelijkheid"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Sneltoetsen"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sneltoetsen aanpassen"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Druk op de toets om de sneltoets toe te wijzen"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Sneltoets verwijderen?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Druk op de toets om de sneltoets toe te wijzen"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hiermee wordt je aangepaste sneltoets definitief verwijderd."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sneltoetsen zoeken"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen zoekresultaten"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icoon voor samenvouwen"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icoon voor actie- of metatoets"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plusicoon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Aanpassen"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klaar"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icoon voor uitvouwen"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handgreep voor slepen"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Toetsenbordinstellingen"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Sneltoets instellen"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Verwijderen"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuleren"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Druk op een toets"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Toetsencombinatie is al in gebruik. Probeer een andere toets."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Toetsencombinatie is al in gebruik. Probeer een andere toets."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Sneltoets kan niet worden ingesteld."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeren met je toetsenbord"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer sneltoetsen die je kunt gebruiken"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeren met je touchpad"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 181315ba045c..28d8cfc41d42 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"କନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ଅଡିଓ ସେୟାରିଂ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ଅଡିଓ ସୁଇଚ କିମ୍ବା ସେୟାର କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ଅଡିଓ ସେୟାରିଂକୁ ସପୋର୍ଟ କରେ"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ସେଭ କରାଯାଇଛି"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ଲକ ସ୍କ୍ରିନ ୱିଜେଟ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ଏକ ୱିଜେଟ ବ୍ୟବହାର କରି ଗୋଟିଏ ଆପ ଖୋଲିବା ପାଇଁ ଏହା ଆପଣ ଅଟନ୍ତି ବୋଲି ଆପଣଙ୍କୁ ଯାଞ୍ଚ କରିବାକୁ ହେବ। ଆହୁରି ମଧ୍ୟ, ଆପଣଙ୍କ ଟାବଲେଟ ଲକ ଥିଲେ ମଧ୍ୟ ଯେ କୌଣସି ବ୍ୟକ୍ତି ଏହାକୁ ଭ୍ୟୁ କରିପାରିବେ ବୋଲି ମନେ ରଖନ୍ତୁ। କିଛି ୱିଜେଟ ଆପଣଙ୍କ ଲକ ସ୍କ୍ରିନ ପାଇଁ ଉଦ୍ଦିଷ୍ଟ ହୋଇନଥାଇପାରେ ଏବଂ ଏଠାରେ ଯୋଗ କରିବା ଅସୁରକ୍ଷିତ ହୋଇପାରେ।"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ବୁଝିଗଲି"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ୱିଜେଟ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍‍ ବଦଳାନ୍ତୁ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍‌ ଓ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ।"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ବାର୍ତ୍ତାଳାପ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ଶୀର୍ଷରେ ଏବଂ ଲକ୍ ସ୍କ୍ରିନରେ ଏକ ପ୍ରୋଫାଇଲ୍ ଛବି ଭାବେ ଦେଖାଏ, ଏକ ବବଲ୍ ଭାବେ ଦେଖାଯାଏ, \'ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\'କୁ ବାଧା ଦିଏ"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ପ୍ରାଥମିକତା"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବାର୍ତ୍ତାଳାପ ଫିଚରଗୁଡ଼ିକୁ ସମର୍ଥନ କରେ ନାହିଁ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ବଣ୍ଡଲ ମତାମତ ପ୍ରଦାନ କରନ୍ତୁ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ପରିବର୍ତ୍ତନ କରିହେବ ନାହିଁ।"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"କଲ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରାଯାଇପାରିବ ନାହିଁ।"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ଏଠାରେ ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ଗ୍ରୁପ୍ କନଫ୍ୟୁଗର୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବା ସମୟରେ ବାମପଟର ବା ଉପରର ଆପକୁ ସୁଇଚ କରନ୍ତୁ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ସମୟରେ: କୌଣସି ଆପକୁ ଗୋଟିଏରୁ ଅନ୍ୟ ଏକ ଆପରେ ବଦଳାନ୍ତୁ"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ସକ୍ରିୟ ୱିଣ୍ଡୋକୁ ଡିସପ୍ଲେଗୁଡ଼ିକ ମଧ୍ୟରେ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ୱିଣ୍ଡୋକୁ ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ୱିଣ୍ଡୋକୁ ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ୱିଣ୍ଡୋକୁ ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ୱିଣ୍ଡୋକୁ ଛୋଟ କରନ୍ତୁ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ଇନପୁଟ"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ପରବର୍ତ୍ତୀ ଭାଷାକୁ ସୁଇଚ କରନ୍ତୁ"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ପୂର୍ବବର୍ତ୍ତୀ ଭାଷାକୁ ସୁଇଚ କରନ୍ତୁ"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>ଟିରୁ କମ କେରେକ୍ଟର ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ବିଲ୍ଡ ନମ୍ୱର"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"କ୍ଲିପବୋର୍ଡକୁ କପି କରାଯାଇଥିବା ବିଲ୍ଡ ନମ୍ୱର।"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"କ୍ଲିପବୋର୍ଡକୁ କପି କରନ୍ତୁ।"</string>
<string name="basic_status" msgid="2315371112182658176">"ବାର୍ତ୍ତାଳାପ ଖୋଲନ୍ତୁ"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ବାର୍ତ୍ତାଳାପ ୱିଜେଟଗୁଡ଼ିକ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ଏକ ବାର୍ତ୍ତାଳାପକୁ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରିବା ପାଇଁ ସେଥିରେ ଟାପ କରନ୍ତୁ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ଉଚ୍ଚ ରିଜୋଲ୍ୟୁସନ ପାଇଁ ଫୋନକୁ ଫ୍ଲିପ କରନ୍ତୁ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ସାମ୍ନା ସ୍କ୍ରିନ ଚାଲୁ ଅଛି"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ଫୋଲ୍ଡେଡ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ଅନଫୋଲ୍ଡେଡ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ସିଷ୍ଟମ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ସିଷ୍ଟମ ଆପ୍ସ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ମଲ୍ଟିଟାସ୍କିଂ"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ବର୍ତ୍ତମାନର ଆପ୍ସ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ଇନପୁଟ"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ଆପ ସର୍ଟକଟ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ଆକ୍ସେସିବିଲିଟୀ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"କୀବୋର୍ଡ ସର୍ଟକଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ସର୍ଟକଟ ଆସାଇନ କରିବା ପାଇଁ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ସର୍ଟକଟକୁ କାଢ଼ି ଦେବେ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ସର୍ଟକଟ ଆସାଇନ କରିବା ପାଇଁ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ଏହା ଆପଣଙ୍କ କଷ୍ଟମ ସର୍ଟକଟକୁ ସ୍ଥାୟୀ ଭାବେ ଡିଲିଟ କରିଦେବ।"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ସର୍ଚ୍ଚ ସର୍ଟକଟ"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"କୌଣସି ସର୍ଚ୍ଚ ଫଳାଫଳ ନାହିଁ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ଆଇକନକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ଆକ୍ସନ କିମ୍ବା ମେଟା କୀ ଆଇକନ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ପ୍ଲସ ଆଇକନ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ହୋଇଗଲା"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ଆଇକନକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"କିମ୍ବା"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ପ୍ଲସ"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ଫରୱାର୍ଡ ସ୍ଲାସ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"କୀବୋର୍ଡ ସେଟିଂ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ସର୍ଟକଟ ସେଟ କରନ୍ତୁ"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ବାତିଲ କରନ୍ତୁ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"କୀ ଦବାନ୍ତୁ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କୀ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କୀ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ସର୍ଟକଟ ସେଟ କରାଯାଇପାରିବ ନାହିଁ।"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ଆପଣଙ୍କ କୀବୋର୍ଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"କୀବୋର୍ଡ ସର୍ଟକଟଗୁଡ଼ିକ ବିଷୟରେ ଜାଣନ୍ତୁ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ଆପଣଙ୍କ ଟଚପେଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 5b9919ef97b6..9df55d6d431a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ਬਲੂਟੁੱਥ ਵਰਤੋ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ਕਨੈਕਟ ਹੈ"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ਆਡੀਓ ਨੂੰ ਸਵਿੱਚ ਜਾਂ ਸਾਂਝਾ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ਲਾਕ ਸਕ੍ਰੀਨ ਵਿਜੇਟ"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ਵਿਜੇਟ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਐਪ ਖੋਲ੍ਹਣ ਲਈ, ਤੁਹਾਨੂੰ ਇਹ ਪੁਸ਼ਟੀ ਕਰਨ ਦੀ ਲੋੜ ਪਵੇਗੀ ਕਿ ਇਹ ਤੁਸੀਂ ਹੀ ਹੋ। ਨਾਲ ਹੀ, ਇਹ ਵੀ ਧਿਆਨ ਵਿੱਚ ਰੱਖੋ ਕਿ ਕੋਈ ਵੀ ਉਨ੍ਹਾਂ ਨੂੰ ਦੇਖ ਸਕਦਾ ਹੈ, ਭਾਵੇਂ ਤੁਹਾਡਾ ਟੈਬਲੈੱਟ ਲਾਕ ਹੋਵੇ। ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੁਝ ਵਿਜੇਟ ਤੁਹਾਡੀ ਲਾਕ ਸਕ੍ਰੀਨ ਲਈ ਨਾ ਬਣੇ ਹੋਣ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਇੱਥੇ ਸ਼ਾਮਲ ਕਰਨਾ ਅਸੁਰੱਖਿਅਤ ਹੋਵੇ।"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ਸਮਝ ਲਿਆ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ਵਿਜੇਟ"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟੇ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ਗੱਲਬਾਤ ਸੂਚਨਾਵਾਂ ਦੇ ਸਿਖਰ \'ਤੇ ਅਤੇ ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰੋਫਾਈਲ ਤਸਵੀਰ ਵਜੋਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ, ਜੋ ਕਿ ਬਬਲ ਵਜੋਂ ਦਿਸਦੀਆਂ ਹਨ ਅਤੇ \'ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ\' ਸੁਵਿਧਾ ਵਿੱਚ ਵਿਘਨ ਵੀ ਪਾ ਸਕਦੀਆਂ ਹਨ"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ਤਰਜੀਹ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਗੱਲਬਾਤ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ਬੰਡਲ ਬਾਰੇ ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਓ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ਇਹਨਾਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਸੋਧਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ਕਾਲ ਸੰਬੰਧੀ ਸੂਚਨਾਵਾਂ ਨੂੰ ਸੋਧਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ਇਹ ਸੂਚਨਾਵਾਂ ਦਾ ਗਰੁੱਪ ਇੱਥੇ ਸੰਰੂਪਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਖੱਬੇ ਜਾਂ ਉੱਪਰ ਮੌਜੂਦ ਐਪ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੌਰਾਨ: ਇੱਕ ਐਪ ਨਾਲ ਦੂਜੀ ਐਪ ਨੂੰ ਬਦਲੋ"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ਕਿਰਿਆਸ਼ੀਲ ਵਿੰਡੋ ਨੂੰ ਇੱਕ ਤੋਂ ਦੂਜੇ ਡਿਸਪਲੇ \'ਤੇ ਲਿਜਾਓ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ਵਿੰਡੋ ਨੂੰ ਖੱਬੇ ਪਾਸੇ ਲਿਜਾਓ"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ਵਿੰਡੋ ਨੂੰ ਸੱਜੇ ਪਾਸੇ ਲਿਜਾਓ"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ਵਿੰਡੋ ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ਵਿੰਡੋ ਨੂੰ ਛੋਟਾ ਕਰੋ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ਇਨਪੁੱਟ"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"ਅਗਲੀ ਭਾਸ਼ਾ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"ਪਿਛਲੀ ਭਾਸ਼ਾ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> ਤੋਂ ਘੱਟ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਵਰਤੋ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ਬਿਲਡ ਨੰਬਰ"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"ਬਿਲਡ ਨੰਬਰ ਨੂੰ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ।"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕਰੋ।"</string>
<string name="basic_status" msgid="2315371112182658176">"ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"ਗੱਲਬਾਤ ਵਿਜੇਟ"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਕਿਸੇ ਗੱਲਬਾਤ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ਉੱਚ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲਈ, ਫ਼ੋਨ ਨੂੰ ਫਲਿੱਪ ਕਰੋ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ਅਗਲੀ ਸਕ੍ਰੀਨ ਚਾਲੂ ਕੀਤੀ ਗਈ"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ਫੋਲਡਯੋਗ ਡੀਵਾਈਸ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ਅਣਫੋਲਡਯੋਗ ਡੀਵਾਈਸ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"ਸਿਸਟਮ ਸੰਬੰਧੀ ਕੰਟਰੋਲ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ਸਿਸਟਮ ਐਪਾਂ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ਮਲਟੀਟਾਸਕਿੰਗ"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ਹਾਲੀਆ ਐਪਾਂ"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ਇਨਪੁੱਟ"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ਐਪ ਸ਼ਾਰਟਕੱਟ"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ਪਹੁੰਚਯੋਗਤਾ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ਸ਼ਾਰਟਕੱਟ ਨਿਰਧਾਰਿਤ ਕਰਨ ਲਈ ਕੁੰਜੀ ਦਬਾਓ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ਕੀ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ਸ਼ਾਰਟਕੱਟ ਨਿਰਧਾਰਿਤ ਕਰਨ ਲਈ ਕੁੰਜੀ ਦਬਾਓ"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ਇਸ ਨਾਲ ਤੁਹਾਡੇ ਵਿਉਂਤੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ਸ਼ਾਰਟਕੱਟ ਖੋਜੋ"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ਕੋਈ ਖੋਜ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ਪ੍ਰਤੀਕ ਨੂੰ ਸਮੇਟੋ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ਕਾਰਵਾਈ ਜਾਂ Meta ਕੁੰਜੀ ਪ੍ਰਤੀਕ"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ਜੋੜ-ਚਿੰਨ੍ਹ ਦਾ ਪ੍ਰਤੀਕ"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ਹੋ ਗਿਆ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ਪ੍ਰਤੀਕ ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ਜਾਂ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ਪਲੱਸ"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ਫਾਰਵਰਡ ਸਲੈਸ਼"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ਕੀ-ਬੋਰਡ ਸੈਟਿੰਗਾਂ"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ਸ਼ਾਰਟਕੱਟ ਸੈੱਟ ਕਰੋ"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ਹਟਾਓ"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ਰੱਦ ਕਰੋ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ਕੁੰਜੀ ਦਬਾਓ"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਕੁੰਜੀ ਨੂੰ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਕੁੰਜੀ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ਆਪਣੇ ਕੀ-ਬੋਰਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ਕੀ-ਬੋਰਡ ਦੇ ਸ਼ਾਰਟਕੱਟਾਂ ਬਾਰੇ ਜਾਣੋ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ਆਪਣੇ ਟੱਚਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index e41d16f82785..9edb183e08d4 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Używaj Bluetootha"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Udostępnianie dźwięku"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Kliknij, aby przełączyć lub udostępnić dźwięk"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Obsługa udostępniania dźwięku"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widżety na ekranie blokady"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Aby otworzyć aplikację za pomocą widżetu, musisz potwierdzić swoją tożsamość. Pamiętaj też, że każdy będzie mógł wyświetlić widżety nawet wtedy, gdy tablet będzie zablokowany. Niektóre widżety mogą nie być przeznaczone do umieszczenia na ekranie blokady i ich dodanie w tym miejscu może być niebezpieczne."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widżety"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wyświetla się u góry powiadomień w rozmowach oraz jako zdjęcie profilowe na ekranie blokady, jako dymek, przerywa działanie trybu Nie przeszkadzać"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priorytetowe"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> nie obsługuje funkcji rozmów"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Prześlij opinię o pakiecie"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Tych powiadomień nie można zmodyfikować."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Powiadomień o połączeniach nie można modyfikować."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Tej grupy powiadomień nie można tu skonfigurować"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Przełącz się na aplikację po lewej lub powyżej na podzielonym ekranie"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Podczas podzielonego ekranu: zastępowanie aplikacji"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Przenieś aktywne okno na inny ekran"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Przenieś okno w lewo"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Przenieś okno w prawo"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksymalizuj okno"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimalizuj okno"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Wprowadzanie"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Przełącz na następny język"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Przełącz na poprzedni język"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Wpisz mniej znaków niż <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numer kompilacji"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Numer kompilacji został skopiowany do schowka."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Kopiuj do schowka"</string>
<string name="basic_status" msgid="2315371112182658176">"Otwarta rozmowa"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widżety rozmów"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Kliknij rozmowę, aby dodać ją do ekranu głównego"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Odwróć telefon, aby uzyskać wyższą rozdzielczość"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ekran przedni jest włączony"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"po zamknięciu"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"po otwarciu"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Systemowe elementy sterujące"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplikacje systemowe"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Wielozadaniowość"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Ostatnie aplikacje"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Podzielony ekran"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Wprowadzanie"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Skróty do aplikacji"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ułatwienia dostępu"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Skróty klawiszowe"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Dostosuj skróty klawiszowe"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Naciśnij klawisz, aby przypisać skrót"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Usunąć skrót?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Naciśnij klawisz, aby przypisać skrót"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Spowoduje to trwałe usunięcie skrótu niestandardowego."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Skróty do wyszukiwania"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Brak wyników wyszukiwania"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zwijania"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona klawisza działania/meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona plusa"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Dostosuj"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotowe"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozwijania"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"lub"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ukośnik prawy"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Uchwyt do przeciągania"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ustawienia klawiatury"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ustaw skrót"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Usuń"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anuluj"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Naciśnij klawisz"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacja klawiszy jest już używana. Użyj innego klawisza."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacja klawiszy jest już używana. Użyj innego klawisza."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nie można ustawić skrótu."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nawiguj za pomocą klawiatury"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Dowiedz się więcej o skrótach klawiszowych"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nawiguj za pomocą touchpada"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 72e593c06718..3871f7ab3c83 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou compartilhar o áudio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com compartilhamento de áudio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da tela de bloqueio"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir um app usando um widget, você precisa confirmar sua identidade. E não se esqueça que qualquer pessoa pode ver os widgets, mesmo com o tablet bloqueado. Além disso, alguns apps não foram criados para a tela de bloqueio, é melhor manter a segurança."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendi"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparecem na parte superior das notificações de conversa, como uma foto do perfil na tela de bloqueio e como um balão. Interrompem o Não perturbe."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
<string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar essas notificações."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamada."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar esse grupo de notificações aqui"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover janela ativa entre telas"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover janela para a esquerda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover janela para a direita"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar janela"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar janela"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para o próximo idioma"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para o idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toque em uma conversa para adicioná-la à tela inicial"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Tela frontal ativada"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controles do sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apps do sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitarefas"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Apps recentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Tela dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar atalhos de teclado"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pressione a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ícone da tecla de ação"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ícone de adição"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"mais"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra para a direita"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configurações do teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atalho"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pressione a tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 460d55304e1e..c0e65c74dea9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partilha de áudio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou partilhar o áudio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com partilha de áudio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets do ecrã de bloqueio"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir uma app através de um widget, vai ter de validar a sua identidade. Além disso, tenha em atenção que qualquer pessoa pode ver os widgets, mesmo quando o tablet estiver bloqueado. Alguns widgets podem não se destinar ao ecrã de bloqueio e pode ser inseguro adicioná-los aqui."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparece na parte superior das notificações de conversas e como uma imagem do perfil no ecrã de bloqueio, surge como um balão, interrompe o modo Não incomodar"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
<string name="no_shortcut" msgid="8257177117568230126">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> não suporta funcionalidades de conversa."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar estas notificações."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamadas."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar este grupo de notificações aqui."</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mude para a app à esquerda ou acima enquanto usa o ecrã dividido"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Durante o ecrã dividido: substituir uma app por outra"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mova a janela ativa entre ecrãs"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover janela para a esquerda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover janela para a direita"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar janela"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar janela"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para idioma seguinte"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> carateres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da compilação"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Número da compilação copiado para a área de transferência."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
<string name="basic_status" msgid="2315371112182658176">"Abrir conversa"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toque numa conversa para a adicionar ao ecrã principal"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução superior, inverta o telemóvel"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável a ser desdobrado"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ecrã frontal ativado"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controlos do sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apps do sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Execução de várias tarefas em simultâneo"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Apps recentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ecrã dividido"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos de teclado"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalize os atalhos de teclado"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Prima a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prima a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta ação elimina o atalho personalizado permanentemente."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado da pesquisa"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone de reduzir"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ícone da tecla Meta ou de ação"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ícone de mais"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone de expandir"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"mais"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Indicador para arrastar"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Definições do teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Configurar atalho"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Prima a tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"A combinação de teclas já está a ser usada. Experimente outra tecla."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"A combinação de teclas já está a ser usada. Experimente outra tecla."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue com o teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos de teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue com o touchpad"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 72e593c06718..3871f7ab3c83 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou compartilhar o áudio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com compartilhamento de áudio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da tela de bloqueio"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir um app usando um widget, você precisa confirmar sua identidade. E não se esqueça que qualquer pessoa pode ver os widgets, mesmo com o tablet bloqueado. Além disso, alguns apps não foram criados para a tela de bloqueio, é melhor manter a segurança."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendi"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparecem na parte superior das notificações de conversa, como uma foto do perfil na tela de bloqueio e como um balão. Interrompem o Não perturbe."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
<string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar essas notificações."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamada."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar esse grupo de notificações aqui"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover janela ativa entre telas"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover janela para a esquerda"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover janela para a direita"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar janela"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar janela"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para o próximo idioma"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para o idioma anterior"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
<string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Toque em uma conversa para adicioná-la à tela inicial"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Tela frontal ativada"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Controles do sistema"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apps do sistema"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitarefas"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Apps recentes"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Tela dividida"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar atalhos de teclado"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pressione a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ícone da tecla de ação"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ícone de adição"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizar"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"mais"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra para a direita"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configurações do teclado"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atalho"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pressione a tecla"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 42fd14b1b9c2..5911c405fcdd 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Permiterea accesului la audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Atinge pentru a comuta sau a permite accesul la conținutul audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Acceptă permiterea accesului la audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgeturi pe ecranul de blocare"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pentru a deschide o aplicație folosind un widget, va trebui să-ți confirmi identitatea. În plus, reține că oricine poate să vadă widgeturile, chiar dacă tableta este blocată. Este posibil ca unele widgeturi să nu fi fost create pentru ecranul de blocare și poate fi nesigur să le adaugi aici."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgeturi"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Se afișează în partea de sus a notificărilor pentru conversații și ca fotografie de profil pe ecranul de blocare, apare ca un balon, întrerupe funcția Nu deranja"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritate"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu acceptă funcții pentru conversații"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Trimite feedback despre pachet"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Aceste notificări nu pot fi modificate."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Notificările pentru apeluri nu pot fi modificate."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Acest grup de notificări nu poate fi configurat aici"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Treci la aplicația din stânga sau de mai sus cu ecranul împărțit"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"În modul ecran împărțit: înlocuiește o aplicație cu alta"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mută fereastra activă de pe un ecran pe altul"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mută fereastra spre stânga"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mută fereastra spre dreapta"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizează fereastra"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizează fereastra"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Introducere"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Comută la următoarea limbă"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Comută la limba anterioară"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Folosește maximum <xliff:g id="LENGTH">%1$d</xliff:g> caractere"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Numărul versiunii s-a copiat în clipboard."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiază în clipboard"</string>
<string name="basic_status" msgid="2315371112182658176">"Deschide conversația"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Widgeturi pentru conversație"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Atinge o conversație ca să o adaugi pe ecranul de pornire"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pentru o rezoluție mai mare, deschide telefonul"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ecranul frontal este activat"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"închis"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"deschis"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Comenzile sistemului"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplicații de sistem"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplicații recente"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ecran împărțit"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Intrare"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Comenzi rapide pentru aplicații"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilitate"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Comenzi rapide de la tastatură"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizează comenzile rapide de la tastatură"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Apasă tasta pentru a atribui comanda rapidă"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Elimini comanda rapidă?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Apasă tasta pentru a atribui comanda rapidă"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Astfel, se va șterge definitiv comanda rapidă personalizată."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Comenzi directe de căutare"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Niciun rezultat al căutării"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Pictograma de restrângere"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Pictograma pentru acțiune sau tastă Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Pictograma plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizează"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gata"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Pictograma de extindere"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"sau"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"bară oblică spre dreapta"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ghidaj de tragere"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Setările tastaturii"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setează o comandă rapidă"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Elimină"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anulează"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Apasă tasta"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinația de taste este deja folosită. Încearcă altă tastă."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinația de taste este deja folosită. Încearcă altă tastă."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Comanda rapidă nu poate fi setată."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navighează folosind tastatura"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Învață comenzile rapide de la tastatură"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navighează folosind touchpadul"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 866cb5e8f602..95c4ee3ffe1f 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Подключено"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Передача аудио"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Нажмите, чтобы переключить аудио или поделиться им"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддерживает передачу аудио"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сохранено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виджеты на заблокированном экране"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Чтобы открыть приложение, используя виджет, вам нужно будет подтвердить свою личность. Обратите внимание, что виджеты видны всем, даже если планшет заблокирован. Некоторые виджеты не предназначены для использования на заблокированном экране. Добавлять их туда может быть небезопасно."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ОК"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеты"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
@@ -698,8 +703,8 @@
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль шума"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Пространственное звучание"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Отключено"</string>
- <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Без отсле­живания"</string>
- <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"С отсле­живанием"</string>
+ <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Статичное"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Динамичное"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Нажмите, чтобы изменить режим звонка."</string>
<string name="volume_ringer_mode" msgid="6867838048430807128">"режим звонка"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"отключить звук"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Появляется в верхней части уведомлений о сообщениях, в виде всплывающего чата, а также в качестве фото профиля на заблокированном экране, прерывает режим \"Не беспокоить\"."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" не поддерживает функции разговоров."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Отправить отзыв о сгруппированных уведомлениях"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Эти уведомления нельзя изменить."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Уведомления о звонках нельзя изменить."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Эту группу уведомлений нельзя настроить здесь."</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Перейти к приложению слева или вверху на разделенном экране"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"В режиме разделения экрана заменить одно приложение другим"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Переместить активное окно между экранами"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Переместить окно влево"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Переместить окно вправо"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Развернуть окно"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Свернуть окно"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ввод"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Выбрать следующий язык"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Выбрать предыдущий язык"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Максимальное количество символов – <xliff:g id="LENGTH">%1$d</xliff:g>."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер сборки"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Номер сборки скопирован в буфер обмена."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"скопировать в буфер обмена."</string>
<string name="basic_status" msgid="2315371112182658176">"Открытый чат"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Виджеты разговоров"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Нажмите на разговор, чтобы добавить его на главный экран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Переверните телефон и используйте основную камеру, чтобы делать снимки с более высоким разрешением."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Передний экран включен"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"устройство сложено"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"устройство разложено"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Управление системой"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системные приложения"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Многозадачность"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Недавние приложения"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Разделение экрана"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Ввод"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Быстрые клавиши для приложений"</string>
@@ -1418,28 +1425,43 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Специальные возможности"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Быстрые клавиши"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Как настроить быстрые клавиши"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Нажмите клавишу, чтобы назначить быструю команду."</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Удалить сочетание клавиш?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Нажмите клавишу, чтобы назначить сочетание клавиш."</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Настроенное сочетание будет безвозвратно удалено."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Найти быстрые клавиши"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ничего не найдено"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Свернуть\""</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Значок клавиши Meta для выполнения действия"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Значок плюса"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Настроить"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Развернуть\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"плюс"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"косая черта"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перемещения"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Настройки клавиатуры"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Задать сочетание клавиш"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Удалить"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Отмена"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Нажмите клавишу"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Это сочетание клавиш уже используется. Попробуйте другое."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Это сочетание клавиш уже используется. Попробуйте другое."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Это сочетание клавиш выбрать нельзя."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигация с помощью клавиатуры"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Узнайте о сочетаниях клавиш."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигация с помощью сенсорной панели"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Узнайте о жестах на сенсорной панели."</string>
<string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Навигация с помощью клавиатуры и сенсорной панели"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Узнайте о жестах на сенсорной панели, сочетаниях клавиш и многом другом."</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Выучите жесты на сенсорной панели, сочетания клавиш и другие варианты навигации."</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Назад"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"На главный экран"</string>
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Просмотр недавних приложений"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 0ccd61c41905..36879d6b7cb0 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"බ්ලූටූත් භාවිතා කරන්න"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"සම්බන්ධිතයි"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ශ්‍රව්‍ය බෙදා ගැනීම"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ශ්‍රව්‍ය මාරු කිරීමට හෝ බෙදා ගැනීමට තට්ටු කරන්න"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ශ්‍රව්‍ය බෙදා ගැනීම සහය දක්වයි"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"සුරැකිණි"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්‍රිය කරන්න"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"අගුළු තිර විජට්"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"විජට් එකක් භාවිතයෙන් යෙදුමක් විවෘත කිරීමට, ඔබට ඒ ඔබ බව සත්‍යාපනය කිරීමට අවශ්‍ය වනු ඇත. එසේම, ඔබේ ටැබ්ලටය අගුළු දමා ඇති විට පවා ඕනෑම කෙනෙකුට ඒවා බැලිය හැකි බව මතක තබා ගන්න. සමහර විජට් ඔබේ අගුළු තිරය සඳහා අදහස් කර නොතිබිය හැකි අතර මෙහි එක් කිරීමට අනාරක්ෂිත විය හැක."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"තේරුණා"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"විජට්"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"සංවාද දැනුම්දීම්වල ඉහළින්ම සහ අගුලු තිරයේ ඇති පැතිකඩ පින්තූරයක් ලෙස පෙන්වයි, බුබුළක් ලෙස දිස් වේ, බාධා නොකරන්න සඳහා බාධා කරයි"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ප්‍රමුඛතාව"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> සංවාද විශේෂාංගවලට සහාය නොදක්වයි"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"බණ්ඩල් ප්‍රතිපෝෂණ ලබා දෙන්න"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"මෙම දැනුම්දීම් වෙනස් කළ නොහැක."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ඇමතුම් දැනුම්දීම් වෙනස් කළ නොහැකිය."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"මෙම දැනුම්දීම් සමූහය මෙහි වින්‍යාස කළ නොහැක"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"බෙදුම් තිරය භාවිත කරන අතරතුර වමේ හෝ ඉහළ ඇති යෙදුමට මාරු වන්න"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"බෙදුම් තිරය අතරතුර: යෙදුමක් එකකින් තවත් එකක් ප්‍රතිස්ථාපනය කරන්න"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"සක්‍රිය කවුළුව සංදර්ශක අතර ගෙන යන්න"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"කවුළුව වමට ගෙන යන්න"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"කවුළුව දකුණට ගෙන යන්න"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"කවුළුව විහිදන්න"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"කවුළුව කුඩා කරන්න"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ආදානය"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"මීළඟ භාෂාවට මාරු වන්න"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"පෙර භාෂාවට මාරු වන්න"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"අනුලකුණු <xliff:g id="LENGTH">%1$d</xliff:g>කට වඩා අඩුවෙන් භාවිතා කරන්න"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"නිමැවුම් අංකය"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"නිමැවුම් අංකය පසුරු පුවරුවට පිටපත් කරන ලදි."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"පසුරු පුවරුවට පිටපත් කරන්න."</string>
<string name="basic_status" msgid="2315371112182658176">"සංවාදය විවෘත කරන්න"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"සංවාද විජට්"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ඔබගේ මුල් තිරයට එය එක් කිරීමට සංවාදයක් තට්ටු කරන්න"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ඉහළ විභේදනය සඳහා, දුරකථනය හරවන්න"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ඉදිරිපස තිරය ක්‍රියාත්මකයි"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"නැවූ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"නොනැවූ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"පද්ධති පාලන"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"පද්ධති යෙදුම්"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"බහුකාර්ය"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"මෑත යෙදුම්"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"බෙදුම් තිරය"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ආදානය"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"යෙදුම් කෙටිමං"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ප්‍රවේශ්‍යතාව"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"යතුරු පුවරු කෙටි මං"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"යතුරුපුවරු කෙටිමං අභිරුචිකරණය කරන්න"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"කෙටි මග පැවරීමට යතුර ඔබන්න"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"කෙටිමඟ ඉවත් කරන්න ද?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"කෙටිමඟ පැවරීමට යතුර ඔබන්න"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"මෙය ඔබේ අභිරුචි කෙටිමඟ ස්ථිරවම මකනු ඇත."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"කෙටි මං සොයන්න"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"සෙවීම් ප්‍රතිඵල නැත"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"හැකුළුම් නිරූපකය"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ක්‍රියාව හෝ Meta යතුරු නිරූපකය"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ධන නිරූපකය"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"අභිරුචිකරණය කරන්න"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"නිමයි"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"දිගහැරීම් නිරූපකය"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"හෝ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ධන"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ඉදිරියට ඉර"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ඇදීම් හැඬලය"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"යතුරු පුවරු සැකසීම්"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"කෙටිමඟ සකසන්න"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ඉවත් කරන්න"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"අවලංගු කරන්න"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"යතුර ඔබන්න"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. වෙනත් යතුරක් උත්සාහ කරන්න."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. වෙනත් යතුරක් උත්සාහ කරන්න."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"කෙටිමඟ සැකසිය නොහැක."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ඔබේ යතුරු පුවරුව භාවිතයෙන් සංචාලනය කරන්න"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"යතුරුපුවරු කෙටිමං ඉගෙන ගන්න"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ඔබේ ස්පර්ශ පෑඩ් භාවිතයෙන් සංචාලනය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index cc59beafe025..c972c180a484 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Používať Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zdieľanie zvuku"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Klepnutím prepnete alebo budete zdieľať zvuk"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podporuje zdieľanie zvuku"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Miniaplikácie na uzamknutej obrazovke"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ak chcete otvoriť aplikáciu pomocou miniaplikácie, budete musieť overiť svoju totožnosť. Pamätajte, že si miniaplikáciu môže pozrieť ktokoľvek, aj keď máte tablet uzamknutý. Niektoré miniaplikácie možno nie sú určené pre uzamknutú obrazovku a ich pridanie tu môže byť nebezpečné."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Dobre"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Miniaplikácie"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Zobrazuje sa ako bublina v hornej časti upozornení konverzácie a profilová fotka na uzamknutej obrazovke, preruší režim bez vyrušení"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritné"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nepodporuje funkcie konverzácie"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Poskytnúť spätnú väzbu k balíku"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Tieto upozornenia sa nedajú upraviť."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Upozornenia na hovory sa nedajú upraviť."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Túto skupinu upozornení nejde na tomto mieste konfigurovať"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prechod na aplikáciu vľavo alebo hore pri rozdelenej obrazovke"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Počas rozdelenej obrazovky: nahradenie aplikácie inou"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Presun aktívneho okna medzi obrazovkami"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Presun okna doľava"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Presun okna doprava"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximalizovanie okna"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimalizovanie okna"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vstup"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Prepnutie na ďalší jazyk"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prepnutie na predchádzajúci jazyk"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Použite menej znakov než <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo zostavy"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Číslo zostavy bolo skopírované do schránky."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"skopírovať do schránky."</string>
<string name="basic_status" msgid="2315371112182658176">"Otvorená konverzácia"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Miniaplikácie konverzácií"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Klepnite na konverzáciu a pridajte ju tak na plochu"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ak chcete vyššie rozlíšenie, prevráťte telefón"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Predná obrazovka je zapnutá"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zložené"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Ovládanie systému"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systémové aplikácie"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nedávne aplikácie"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Rozdelená obrazovka"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vstup"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Skratky aplikácií"</string>
@@ -1418,27 +1425,42 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostupnosť"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové skratky"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prispôsobenie klávesových skratiek"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Stlačením klávesa priraďte skratku"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Chcete skratku odstrániť?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Stlačením klávesa priraďte skratku"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Týmto natrvalo odstránite vlastnú skratku."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prehľadávať skratky"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žiadne výsledky vyhľadávania"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zbalenia"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona akčného klávesa alebo metaklávesa"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Prispôsobiť"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hotovo"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalenia"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"alebo"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"lomka"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Presúvadlo"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavenia klávesnice"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastaviť skratku"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstrániť"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Zrušiť"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Stlačte kláves"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinácia klávesov sa už používa. Skúste iný kláves."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinácia klávesov sa už používa. Skúste iný kláves."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Skratku nie je možné nastaviť."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Pohybujte sa v systéme pomocou klávesnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte sa klávesové skratky"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Pohybujte sa v systéme pomocou touchpadu"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Naučte sa gestá touchpadu"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Prechádzajte pomocou klávesnice a touchpadu"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Pohybujte sa v systéme pomocou klávesnice a touchpadu"</string>
<string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Naučte sa gestá touchpadu, klávesové skratky a ďalšie funkcie"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Prechod späť"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Prejsť na plochu"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 38f1e5a1fb6d..914df7219f25 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvoka"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dotaknite se za preklop ali deljenje zvoka"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podpira deljenje zvoka"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Pripomočki na zaklenjenem zaslonu"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Če želite aplikacijo odpreti s pripomočkom, morate potrditi, da ste to vi. Upoštevajte tudi, da si jih lahko ogledajo vsi, tudi ko je tablični računalnik zaklenjen. Nekateri pripomočki morda niso predvideni za uporabo na zaklenjenem zaslonu, zato jih tukaj morda ni varno dodati."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Razumem"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Pripomočki"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikaz v obliki oblačka na vrhu razdelka z obvestili za pogovor in kot profilna slika na zaklenjenem zaslonu, preglasitev načina Ne moti."</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prednostno"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podpira pogovornih funkcij."</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošiljanje povratnih informacij v paketu"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Za ta obvestila ni mogoče spremeniti nastavitev."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obvestil o klicih ni mogoče spreminjati."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Te skupine obvestil ni mogoče konfigurirati tukaj"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Preklop na aplikacijo levo ali zgoraj med uporabo razdeljenega zaslona"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Pri razdeljenem zaslonu: medsebojna zamenjava aplikacij"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premikanje aktivnega okna med zasloni"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Premik okna na levo"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Premik okna na desno"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Povečanje okna"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Pomanjšanje okna"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vnos"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Preklop na naslednji jezik"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Preklop na prejšnji jezik"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Uporabite manj kot <xliff:g id="LENGTH">%1$d</xliff:g> znakov."</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Delovna različica"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Delovna različica je bila kopirana v odložišče."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje v odložišče."</string>
<string name="basic_status" msgid="2315371112182658176">"Odprt pogovor"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Pripomočki za pogovore"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Dotaknite se pogovora, da ga dodate na začetni zaslon."</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višjo ločljivost obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Sprednji zaslon je vklopljen"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zaprto"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"razprto"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistemski kontrolniki"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistemske aplikacije"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Večopravilnost"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Nedavne aplikacije"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Razdeljen zaslon"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vnos"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Bližnjice do aplikacij"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostopnost"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Bližnjične tipke"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagajanje bližnjičnih tipk"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipko za dodelitev bližnjice"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite odstraniti bližnjico?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipko za dodelitev bližnjice"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"S tem boste trajno izbrisali bližnjico po meri."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Iskanje po bližnjicah"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ni rezultatov iskanja"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za strnitev"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona tipke za dejanje ali metapodatke"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona znaka plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Prilagodi"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Končano"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za razširitev"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ali"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"poševnica naprej"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ročica za vlečenje"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavitve tipkovnice"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastavite bližnjico"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstrani"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Prekliči"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipko"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tipk je že v uporabi. Poskusite z drugo tipko."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tipk je že v uporabi. Poskusite z drugo tipko."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Bližnjice ni mogoče nastaviti."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krmarjenje s tipkovnico"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Učenje bližnjičnih tipk"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krmarjenje s sledilno ploščico"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 9ed8bf29d630..6a597f7bd8f8 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ndarja e audios"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Trokit për të ndërruar ose ndarë audion"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Mbështet ndarjen e audios"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Miniaplikacionet në ekranin e kyçjes"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Për të hapur një aplikacion duke përdorur një miniaplikacion, do të duhet të verifikosh që je ti. Ki parasysh gjithashtu që çdo person mund t\'i shikojë, edhe kur tableti yt është i kyçur. Disa miniaplikacione mund të mos jenë planifikuar për ekranin tënd të kyçjes dhe mund të mos jetë e sigurt t\'i shtosh këtu."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"E kuptova"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Miniaplikacionet"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shfaqet në krye të njoftimeve të bisedës dhe si fotografia e profilit në ekranin e kyçjes, shfaqet si flluskë dhe ndërpret modalitetin \"Mos shqetëso\""</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Me përparësi"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk mbështet veçoritë e bisedës"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Jep komente për paketën"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Këto njoftime nuk mund të modifikohen."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Njoftimet e telefonatave nuk mund të modifikohen."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ky grup njoftimesh nuk mund të konfigurohet këtu"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Kalo tek aplikacioni në të majtë ose sipër kur përdor ekranin e ndarë"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Gjatë ekranit të ndarë: zëvendëso një aplikacion me një tjetër"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Zhvendose dritaren aktive mes ekraneve"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Zhvendos dritaren në të majtë"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Zhvendos dritaren në të djathtë"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maksimizo dritaren"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizo dritaren"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Hyrja"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Kalo te gjuha tjetër"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Kalo te gjuha e mëparshme"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Përdor më pak se <xliff:g id="LENGTH">%1$d</xliff:g> karaktere"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numri i ndërtimit"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Numri i ndërtimit u kopjua te kujtesa e fragmenteve"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopjo në kujtesën e fragmenteve."</string>
<string name="basic_status" msgid="2315371112182658176">"Hap bisedën"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Miniaplikacionet e bisedave"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Trokit te një bisedë dhe shtoje në ekranin bazë"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Për rezolucion më të lartë, përmbys telefonin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ekrani i përparmë është aktivizuar"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"palosur"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"shpalosur"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Kontrollet e sistemit"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Aplikacionet e sistemit"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Kryerja e shumë detyrave"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Aplikacionet e fundit"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ekrani i ndarë"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Hyrja"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Shkurtoret e aplikacionit"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qasshmëria"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Shkurtoret e tastierës"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizo shkurtoret e tastierës"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Shtyp tastin për të caktuar shkurtoren"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Të hiqet shkurtorja?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Shtyp tastin për të caktuar shkurtoren"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Kjo do ta fshijë përgjithmonë shkurtoren tënde të personalizuar."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kërko për shkurtoret"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Asnjë rezultat kërkimi"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona e palosjes"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikona e tastit të veprimit ose tastit Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Ikona e plusit"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Personalizo"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"U krye"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona e zgjerimit"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ose"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"vizë e pjerrët djathtas"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Doreza e zvarritjes"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Cilësimet e tastierës"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Cakto shkurtoren"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Hiq"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anulo"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Shtyp tastin"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinimi i tasteve është tashmë në përdorim. Provo një tast tjetër."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinimi i tasteve është tashmë në përdorim. Provo një tast tjetër."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shkurtorja nuk mund të caktohet."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigo duke përdorur tastierën tënde"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Mëso shkurtoret e tastierës"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigo duke përdorur bllokun me prekje"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 102c452fc54a..560f4d700d22 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Повезано"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Дељење звука"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Додирните да бисте пребацили или делили звук"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Подржава дељење звука"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сачувано"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виџети за закључани екран"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Да бисте отворили апликацију која користи виџет, треба да потврдите да сте то ви. Имајте у виду да свако може да га види, чак и када је таблет закључан. Неки виџети можда нису намењени за закључани екран и можда није безбедно да их тамо додате."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Важи"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виџети"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Приказује се у врху обавештења о конверзацијама и као слика профила на закључаном екрану, појављује се као облачић, прекида режим Не узнемиравај"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Приоритетно"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не подржава функције конверзације"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пружите повратне информације о скупу"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ова обавештења не могу да се мењају."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Обавештења о позивима не могу да се мењају."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ова група обавештења не може да се конфигурише овде"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пређите у апликацију слева или изнад док користите подељени екран"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"У режиму подељеног екрана: замена једне апликације другом"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Премести активан прозор на следећи екран"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Померите прозор налево"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Померите прозор надесно"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Повећајте прозор"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Смањите прозор"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Унос"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Пређи на следећи језик"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Пређи на претходни језик"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Користите мањи број знакова од <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Број верзије"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Број верзије је копиран у привремену меморију."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копирајте у привремену меморију."</string>
<string name="basic_status" msgid="2315371112182658176">"Отворите конверзацију"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Виџети за конверзацију"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Додирните конверзацију да бисте је додали на почетни екран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За већу резолуцију обрните телефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предњи екран је укључен"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Системске контроле"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системске апликације"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Обављање више задатака истовремено"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Недавне апликације"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Подељени екран"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Унос"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Пречице за апликације"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Приступачност"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Тастерске пречице"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Прилагодите тастерске пречице"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Притисните тастер да бисте доделили пречицу"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Желите да уклоните пречицу?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притисните тастер да бисте доделили пречицу"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Овим ћете трајно избрисати прилагођену пречицу."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Претражите пречице"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултата претраге"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за скупљање"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Икона тастера за радњу или мета тастера"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Икона знака плус"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Прилагоди"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширивање"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"плус"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"коса црта унапред"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер за превлачење"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Подешавања тастатуре"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Подеси пречицу"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Уклони"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Откажи"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Притисните тастер"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбинација тастера се већ користи. Пробајте са другим тастером."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинација тастера се већ користи. Пробајте са другим тастером."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Подешавање пречице није успело."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Крећите се помоћу тастатуре"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Сазнајте више о тастерским пречицама"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Крећите се помоћу тачпеда"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c2ac21691aa1..2996033d36ad 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ljuddelning"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tryck för att byta eller dela ljud"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ljuddelning stöds"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgetar för låsskärm"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Du måste verifiera din identitet innan du öppnar en app med en widget. Tänk också på att alla kan se dem, även när surfplattan är låst. Vissa widgetar kanske inte är avsedda för låsskärmen och det kan vara osäkert att lägga till dem här."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetar"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Visas högst upp i konversationsaviseringarna och som profilbild på låsskärmen, visas som bubbla, åsidosätter Stör ej"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> har inte stöd för konversationsfunktioner"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Ge feedback om paket"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Det går inte att ändra de här aviseringarna."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Det går inte att ändra samtalsaviseringarna."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Den här aviseringsgruppen kan inte konfigureras här"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Byt till appen till vänster eller ovanför när du använder delad skärm"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Med delad skärm: ersätt en app med en annan"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flytta det aktiva fönstret mellan skärmar"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Flytta fönstret åt vänster"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Flytta fönstret åt höger"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximera fönstret"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimera fönstret"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Inmatning"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Byt till nästa språk"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Byt till föregående språk"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Använd färre än <xliff:g id="LENGTH">%1$d</xliff:g> tecken"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Versionsnummer"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Versionsnumret har kopierats till urklipp."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiera till urklipp."</string>
<string name="basic_status" msgid="2315371112182658176">"Öppen konversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Konversationswidgetar"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Tryck på en konversation för att lägga till den på startskärmen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vänd telefonen för högre upplösning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Den främre skärmen har aktiverats"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"hopvikt"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"uppvikt"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Systeminställningar"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Systemappar"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multikörning"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Senaste apparna"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Delad skärm"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Inmatning"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Genvägar till appar"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tillgänglighet"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Kortkommandon"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Anpassa kortkommandon"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tryck på tangenten för att ange kortkommando"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vill du ta bort kortkommandot?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tryck på tangenten för att tilldela ett kortkommando"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Det anpassade kortkommandot raderas permanent."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sökgenvägar"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Inga sökresultat"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikonen Komprimera"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Ikon för åtgärdstangent"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plusikon"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Anpassa"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klar"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikonen Utöka"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"snedstreck"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handtag"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tangentbordsinställningar"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ange kortkommando"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ta bort"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Avbryt"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tryck på tangenten"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tangentkombinationen används redan. Testa en annan tangent."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tangentkombinationen används redan. Testa en annan tangent."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Det går inte att ställa in kortkommandot."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigera med tangentbordet"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lär dig kortkommandon"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigera med styrplattan"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 3418907ea44c..7084bbf4d6a6 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Kusikiliza Pamoja"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Gusa ili ubadilishe sauti au usikilize pamoja na wengine"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Inatumia kipengele cha kusikiliza pamoja"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Wijeti zinazoonekana kwenye skrini iliyofungwa"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Utahitaji kuthibitisha kuwa ni wewe ili ufungue programu ukitumia wijeti. Pia, kumbuka kuwa mtu yeyote anaweza kuziona, hata kishikwambi chako kikiwa kimefungwa. Huenda baadhi ya wijeti hazikukusudiwa kutumika kwenye skrini yako iliyofungwa na huenda si salama kuziweka hapa."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Nimeelewa"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Wijeti"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Huonyeshwa kwenye sehemu ya juu ya arifa za mazungumzo na kama picha ya wasifu kwenye skrini iliyofungwa. Huonekana kama kiputo na hukatiza kipengele cha Usinisumbue"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Kipaumbele"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> haitumii vipengele vya mazungumzo"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Toa Maoni kuhusu Kifurushi"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Arifa hizi haziwezi kubadilishwa."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Arifa za simu haziwezi kubadilishwa."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Kikundi hiki cha arifa hakiwezi kuwekewa mipangilio hapa"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Badilisha uende kwenye programu iliyo kushoto au juu unapotumia hali ya kugawa skrini"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ukigawanya skrini: badilisha kutoka programu moja hadi nyingine"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Hamisha dirisha linalotumika kati ya skrini moja na nyingine"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Sogeza dirisha kushoto"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Sogeza dirisha kulia"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Panua dirisha"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Punguza dirisha"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vifaa vya kuingiza data"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Badilisha utumie lugha inayofuata"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Badilisha utumie lugha iliyotangulia"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Hupaswi kuzidi herufi <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nambari ya muundo"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Nambari ya muundo imewekwa kwenye ubao wa kunakili."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Nakili kwenye ubao wa kunakili"</string>
<string name="basic_status" msgid="2315371112182658176">"Fungua mazungumzo"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Wijeti za mazungumzo"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Gusa mazungumzo ili uyaweke kwenye Skrini yako ya kwanza"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kwa ubora wa juu, geuza simu"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Umewasha skrini ya mbele"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kimekunjwa"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kimefunguliwa"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Vidhibiti vya mfumo"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Programu za mfumo"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Majukumu mengi"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Programu za hivi majuzi"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Gawa skrini"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Kifaa cha kuingiza data"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Njia za mikato za programu"</string>
@@ -1418,28 +1425,43 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ufikivu"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Mikato ya kibodi"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Weka mapendeleo ya mikato ya kibodi"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Bonyeza kitufe ukabidhi njia ya mkato"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ungependa kuondoa njia ya mkato?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Bonyeza kitufe ukabidhi njia ya mkato"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hatua hii itaondoa kabisa njia yako maalum ya mkato."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Njia mkato za kutafutia"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hamna matokeo ya utafutaji"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kunja aikoni"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Aikoni ya kitufe cha Vitendo au cha Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Aikoni ya alama ya kujumlisha"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Weka mapendeleo"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Nimemaliza"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Panua aikoni"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"au"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"na"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"mkwaju wa mbele"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Aikoni ya buruta"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Mipangilio ya Kibodi"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Weka njia ya mkato"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ondoa"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Acha"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Bonyeza kitufe"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tayari unatumia mchanganyiko huu wa vitufe. Jatibu kitufe kingine."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tayari unatumia mchanganyiko wa vitufe. Jaribu kitufe kingine."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Haiwezi kuweka njia ya mkato."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Kusogeza kwa kutumia kibodi yako"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Fahamu kuhusu mikato ya kibodi"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Kusogeza kwa kutumia padi yako ya kugusa"</string>
<string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Fahamu miguso ya padi ya kugusa"</string>
<string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Kusogeza kwa kutumia kibodi na padi yako ya kugusa"</string>
- <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Jifunze kuhusu miguso ya padi ya kugusa, mikato ya kibodi na mengineyo"</string>
+ <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Fahamu kuhusu miguso ya padi ya kugusa, mikato ya kibodi na mengineyo"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Rudi nyuma"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Nenda kwenye ukurasa wa mwanzo"</string>
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Angalia programu za hivi majuzi"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 99c912ca072a..959e2e6d07ca 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -117,7 +117,7 @@
<string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ஓர் ஆப்ஸை ரெக்கார்டு செய்யும்போது அதில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தும் ரெக்கார்டு செய்யப்படும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"திரையை ரெக்கார்டு செய்"</string>
<string name="screenrecord_app_selector_title" msgid="3854492366333954736">"ரெக்கார்டு செய்ய ஆப்ஸைத் தேர்வுசெய்தல்"</string>
- <string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்"</string>
+ <string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்தல்"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"சாதன ஆடியோ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"இசை, அழைப்புகள், ரிங்டோன்கள் போன்ற உங்கள் சாதனத்திலிருந்து வரும் ஒலி"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"மைக்ரோஃபோன்"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"புளூடூத்தைப் பயன்படுத்துதல்"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"இணைக்கப்பட்டது"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ஆடியோ பகிர்வு"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ஆடியோவை மாற்ற அல்லது பகிர, தட்டவும்"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ஆடியோ பகிர்வை ஆதரிக்கிறது"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"பூட்டுத் திரை விட்ஜெட்கள்"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"விட்ஜெட்டைப் பயன்படுத்தி ஆப்ஸைத் திறக்க, அது நீங்கள்தான் என்பதை உறுதிசெய்ய வேண்டும். அத்துடன், உங்கள் டேப்லெட் பூட்டப்பட்டிருந்தாலும்கூட அவற்றை யார் வேண்டுமானாலும் பார்க்கலாம் என்பதை நினைவில்கொள்ளுங்கள். சில விட்ஜெட்கள் உங்கள் பூட்டுத் திரைக்காக உருவாக்கப்பட்டவை அல்ல என்பதையும் அவற்றை இங்கே சேர்ப்பது பாதுகாப்பற்றதாக இருக்கக்கூடும் என்பதையும் நினைவில்கொள்ளுங்கள்."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"சரி"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"விட்ஜெட்கள்"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"உரையாடல் அறிவிப்புகளின் மேற்பகுதியில் காட்டப்படும், திரை பூட்டப்பட்டிருக்கும்போது சுயவிவரப் படமாகக் காட்டப்படும், குமிழாகத் தோன்றும், தொந்தரவு செய்ய வேண்டாம் அம்சம் இயக்கப்பட்டிருக்கும்போதும் காட்டப்படும்"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"முன்னுரிமை"</string>
<string name="no_shortcut" msgid="8257177117568230126">"உரையாடல் அம்சங்களை <xliff:g id="APP_NAME">%1$s</xliff:g> ஆதரிக்காது"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"மொத்தமாகக் கருத்தை வழங்கு"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"இந்த அறிவிப்புகளை மாற்ற இயலாது."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"அழைப்பு அறிவிப்புகளை மாற்ற முடியாது."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"இந்த அறிவுப்புக் குழுக்களை இங்கே உள்ளமைக்க இயலாது"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"திரைப் பிரிப்பைப் பயன்படுத்தும்போது இடது/மேலே உள்ள ஆப்ஸுக்கு மாறுதல்"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"திரைப் பிரிப்பின்போது: ஓர் ஆப்ஸுக்குப் பதிலாக மற்றொன்றை மாற்றுதல்"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"காட்சிகளுக்கு இடையே செயலில் உள்ள சாளரத்தை நகர்த்துதல்"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"சாளரத்தை இடதுபுறமாக நகர்த்துதல்"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"சாளரத்தை வலதுபுறமாக நகர்த்துதல்"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"சாளரத்தைப் பெரிதாக்குதல்"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"சாளரத்தைச் சிறிதாக்குதல்"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"உள்ளீடு"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"அடுத்த மொழிக்கு மாற்றுதல்"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"முந்தைய மொழிக்கு மாற்றுதல்"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> எழுத்துகளுக்குக் குறைவாகப் பயன்படுத்துங்கள்"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"பதிப்பு எண்"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"பதிப்பு எண் கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"கிளிப்போர்டுக்கு நகலெடுக்கும்."</string>
<string name="basic_status" msgid="2315371112182658176">"திறந்தநிலை உரையாடல்"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"உரையாடல் விட்ஜெட்டுகள்"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ஓர் உரையாடலை உங்கள் முகப்புத் திரையில் சேர்க்க அந்த உரையாடலைத் தட்டுங்கள்"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"உயர் தெளிவுத்திறனுக்கு, மொபைலை ஃபிளிப் செய்யுங்கள்"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"முன்பக்கத் திரை இயக்கப்பட்டுள்ளது"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"மடக்கப்பட்டது"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"விரிக்கப்பட்டது"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"சிஸ்டம் கட்டுப்பாடுகள்"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"சிஸ்டம் ஆப்ஸ்"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"பல வேலைகளைச் செய்தல்"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"சமீபத்திய ஆப்ஸ்"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"திரைப் பிரிப்பு"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"உள்ளீடு"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ஆப்ஸ் ஷார்ட்கட்கள்"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"மாற்றுத்திறன் வசதி"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"கீபோர்டு ஷார்ட்கட்கள்"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"கீபோர்டு ஷார்ட்கட்களைப் பிரத்தியேகப்படுத்துதல்"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ஷார்ட்கட்டை அமைக்க பட்டனை அழுத்துங்கள்"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ஷார்ட்கட்டை அகற்றவா?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ஷார்ட்கட்டை அமைக்க பட்டனை அழுத்துங்கள்"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"இது உங்கள் பிரத்தியேக ஷார்ட்கட்டை நிரந்தரமாக நீக்கும்."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ஷார்ட்கட்களைத் தேடுக"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"தேடல் முடிவுகள் இல்லை"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"சுருக்குவதற்கான ஐகான்"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ஆக்‌ஷன்/மெட்டா பட்டன் ஐகான்"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"பிளஸ் ஐகான்"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"பிரத்தியேகப்படுத்தும்"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"முடிந்தது"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"விரிவாக்குவதற்கான ஐகான்"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"அல்லது"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"மற்றும்"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ஃபார்வர்டு ஸ்லாஷ்"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"இழுப்பதற்கான ஹேண்டில்"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"கீபோர்டு அமைப்புகள்"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ஷார்ட்கட்டை அமையுங்கள்"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"அகற்று"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ரத்துசெய்"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"பட்டனை அழுத்துங்கள்"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறொரு பட்டனைப் பயன்படுத்திப் பார்க்கவும்."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறொரு பட்டனைப் பயன்படுத்திப் பார்க்கவும்."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ஷார்ட்கட்டை அமைக்க முடியாது."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"கீபோர்டைப் பயன்படுத்திச் செல்லுதல்"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"கீபோர்டு ஷார்ட்கட்கள் குறித்துத் தெரிந்துகொள்ளுங்கள்"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"டச்பேடைப் பயன்படுத்திச் செல்லுதல்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 5a088231c945..a66821b05386 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ఆడియో షేరింగ్"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"ఆడియోను మార్చడానికి లేదా షేర్ చేయడానికి ట్యాప్ చేయండి"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ఆడియో షేరింగ్‌కు సపోర్ట్ చేస్తుంది"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ అయ్యింది"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్‌కనెక్ట్ చేయండి"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"లాక్ స్క్రీన్ విడ్జెట్‌లు"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"విడ్జెట్‌ను ఉపయోగించి యాప్‌ను తెరవడానికి, ఇది మీరేనని వెరిఫై చేయాల్సి ఉంటుంది. అలాగే, మీ టాబ్లెట్ లాక్ చేసి ఉన్నప్పటికీ, ఎవరైనా వాటిని చూడగలరని గుర్తుంచుకోండి. కొన్ని విడ్జెట్‌లు మీ లాక్ స్క్రీన్‌కు తగినవి కాకపోవచ్చు, వాటిని ఇక్కడ జోడించడం సురక్షితం కాకపోవచ్చు."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"అర్థమైంది"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"విడ్జెట్‌లు"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్‌డౌన్ మెనూ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్‌లోని అన్ని యాప్‌లు మరియు డేటా తొలగించబడతాయి."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"సంభాషణ నోటిఫికేషన్‌ల ఎగువున, లాక్ స్క్రీన్‌లో ప్రొఫైల్ ఫోటో‌గా చూపిస్తుంది, బబుల్‌గా కనిపిస్తుంది, \'అంతరాయం కలిగించవద్దు\'ను అంతరాయం కలిగిస్తుంది"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ప్రాధాన్యత"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> సంభాషణ ఫీచర్‌లను సపోర్ట్ చేయదు"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"బండిల్ ఫీడ్‌బ్యాక్‌ను అందించండి"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ఈ నోటిఫికేషన్‌లను ఎడిట్ చేయడం వీలుపడదు."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"కాల్ నోటిఫికేషన్‌లను ఎడిట్ చేయడం సాధ్యం కాదు."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"ఈ నోటిఫికేషన్‌ల గ్రూప్‌ను ఇక్కడ కాన్ఫిగర్ చేయలేము"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"స్ప్లిట్ స్క్రీన్ ఉపయోగిస్తున్నప్పుడు ఎడమ లేదా పైన యాప్‌నకు మారండి"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"స్ప్లిట్ స్క్రీన్ సమయంలో: ఒక దాన్నుండి మరో దానికి యాప్ రీప్లేస్ చేయండి"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"యాక్టివ్ విండోను డిస్‌ప్లేల మధ్య తరలించండి"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"విండోను ఎడమ వైపునకు తరలించండి"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"విండోను కుడి వైపునకు తరలించండి"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"విండోను విస్తరించండి"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"విండోను కుదించండి"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ఇన్‌పుట్"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"తర్వాత భాషకు స్విచ్ అవ్వండి"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"మునుపటి భాషకు స్విచ్ అవ్వండి"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> కంటే తక్కువ అక్షరాలను ఉపయోగించండి"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"బిల్డ్ నంబర్"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"బిల్డ్ నంబర్, క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"క్లిప్‌బోర్డ్‌కు కాపీ చేయండి."</string>
<string name="basic_status" msgid="2315371112182658176">"సంభాషణను తెరవండి"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"సంభాషణ విడ్జెట్‌లు"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"ఏదైనా సంభాషణను మీ మొదటి స్క్రీన్‌కు జోడించడానికి దానిని ట్యాప్ చేయండి"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"అధిక రిజల్యూషన్ కోసం, ఫోన్‌ను తిప్పండి"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ముందు వైపు స్క్రీన్ ఆన్ అయింది"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"మడిచే సదుపాయం గల పరికరం"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"మడిచే సదుపాయం లేని పరికరం"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"సిస్టమ్ కంట్రోల్స్"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"సిస్టమ్ యాప్‌లు"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"మల్టీ-టాస్కింగ్"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"ఇటీవలి యాప్‌లు"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"స్ప్లిట్ స్క్రీన్"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ఇన్‌పుట్"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"యాప్ షార్ట్‌కట్‌లు"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"యాక్సెసిబిలిటీ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"కీబోర్డ్ షార్ట్‌కట్‌లు"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"కీబోర్డ్ షార్ట్‌కట్‌లను అనుకూలంగా మార్చండి"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"షార్ట్‌కట్‌ను కేటాయించడానికి కీని నొక్కండి"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"షార్ట్‌కట్‌ను తీసివేయాలా?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"షార్ట్‌కట్‌ను కేటాయించడానికి కీని నొక్కండి"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ఇది మీ అనుకూల షార్ట్‌కట్‌ను శాశ్వతంగా తొలగిస్తుంది."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"షార్ట్‌కట్‌లను వెతకండి"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"సెర్చ్ ఫలితాలు ఏవీ లేవు"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"కుదించండి చిహ్నం"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"యాక్షన్ లేదా మెటా కీ చిహ్నం"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ప్లస్ చిహ్నం"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"అనుకూలంగా మార్చండి"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"పూర్తయింది"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"విస్తరించండి చిహ్నం"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"లేదా"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ప్లస్ గుర్తు"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ఫార్వర్డ్ స్లాష్"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"లాగే హ్యాండిల్"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"కీబోర్డ్ సెట్టింగ్‌లు"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"షార్ట్‌కట్‌ను సెట్ చేయండి"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"తీసివేయండి"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"రద్దు చేయండి"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"కీని నొక్కండి"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. వేరొక కీని ట్రై చేయండి."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. వేరొక కీని ట్రై చేయండి."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"షార్ట్‌కట్‌ను సెట్ చేయడం సాధ్యం కాదు."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"మీ కీబోర్డ్ ఉపయోగించి నావిగేట్ చేయండి"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"కీబోర్డ్ షార్ట్‌కట్‌ల గురించి తెలుసుకోండి"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"మీ టచ్‌ప్యాడ్‌ను ఉపయోగించి నావిగేట్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 816514a9797f..5ead29f4d155 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ใช้บลูทูธ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"เชื่อมต่อแล้ว"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"การแชร์เสียง"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"แตะเพื่อสลับหรือแชร์เสียง"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"รองรับการแชร์เสียง"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"บันทึกแล้ว"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"วิดเจ็ตในหน้าจอล็อก"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"หากต้องการเปิดแอปโดยใช้วิดเจ็ต คุณจะต้องยืนยันตัวตนของคุณ นอกจากนี้ โปรดทราบว่าผู้อื่นจะดูวิดเจ็ตเหล่านี้ได้แม้ว่าแท็บเล็ตจะล็อกอยู่ก็ตาม วิดเจ็ตบางอย่างอาจไม่ได้มีไว้สำหรับหน้าจอล็อกของคุณ และอาจไม่ปลอดภัยที่จะเพิ่มที่นี่"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"รับทราบ"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"วิดเจ็ต"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"แสดงที่ด้านบนของการแจ้งเตือนการสนทนาและเป็นรูปโปรไฟล์บนหน้าจอล็อก ปรากฏเป็นบับเบิล แสดงในโหมดห้ามรบกวน"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"สำคัญ"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่รองรับฟีเจอร์การสนทนา"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"แสดงความคิดเห็นเกี่ยวกับแพ็กเกจ"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"แก้ไขการแจ้งเตือนเหล่านี้ไม่ได้"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"แก้ไขการแจ้งเตือนสายเรียกเข้าไม่ได้"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"การแจ้งเตือนกลุ่มนี้กำหนดค่าที่นี่ไม่ได้"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"เปลี่ยนไปใช้แอปทางด้านซ้ายหรือด้านบนขณะใช้โหมดแยกหน้าจอ"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"ระหว่างใช้โหมดแยกหน้าจอ: เปลี่ยนแอปหนึ่งเป็นอีกแอปหนึ่ง"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ย้ายหน้าต่างที่ใช้งานไปยังหน้าจอต่างๆ"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ย้ายหน้าต่างไปทางซ้าย"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ย้ายหน้าต่างไปทางขวา"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ขยายหน้าต่างเต็มหน้าจอ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ย่อหน้าต่าง"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"อินพุต"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"เปลี่ยนเป็นภาษาถัดไป"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"เปลี่ยนเป็นภาษาก่อนหน้า"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ใช้อักขระไม่เกิน <xliff:g id="LENGTH">%1$d</xliff:g> ตัว"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"หมายเลขบิลด์"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"คัดลอกหมายเลขบิลด์ไปยังคลิปบอร์ดแล้ว"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"คัดลอกไปยังคลิปบอร์ด"</string>
<string name="basic_status" msgid="2315371112182658176">"เปิดการสนทนา"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"วิดเจ็ตการสนทนา"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"แตะการสนทนาเพื่อเพิ่มไปยังหน้าจอหลัก"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"พลิกด้านโทรศัพท์เพื่อให้ได้ภาพที่มีความละเอียดมากขึ้น"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"เปิดหน้าจอด้านหน้าแล้ว"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"พับ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"กางออก"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"การควบคุมระบบ"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"แอประบบ"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"การทํางานหลายอย่างพร้อมกัน"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"แอปล่าสุด"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"แยกหน้าจอ"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"อินพุต"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"แป้นพิมพ์ลัดของแอป"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"การช่วยเหลือพิเศษ"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"แป้นพิมพ์ลัด"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ปรับแต่งแป้นพิมพ์ลัด"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"กดแป้นเพื่อกำหนดแป้นพิมพ์ลัด"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"นำแป้นพิมพ์ลัดออกใช่ไหม"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"กดแป้นเพื่อกำหนดแป้นพิมพ์ลัด"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"การดำเนินการนี้จะลบแป้นพิมพ์ลัดที่กำหนดเองอย่างถาวร"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ค้นหาแป้นพิมพ์ลัด"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ไม่พบผลการค้นหา"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ไอคอนยุบ"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"ไอคอนการดำเนินการหรือแป้น Meta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"ไอคอนเครื่องหมายบวก"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"ปรับแต่ง"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"เสร็จสิ้น"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ไอคอนขยาย"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"หรือ"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"บวก"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"เครื่องหมายทับ"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"แฮนเดิลการลาก"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"การตั้งค่าแป้นพิมพ์"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ตั้งค่าแป้นพิมพ์ลัด"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"นำออก"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ยกเลิก"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"กดแป้น"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้แป้นอื่น"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้แป้นอื่น"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ตั้งค่าแป้นพิมพ์ลัดไม่ได้"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ไปยังส่วนต่างๆ โดยใช้แป้นพิมพ์"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ดูข้อมูลเกี่ยวกับแป้นพิมพ์ลัด"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ไปยังส่วนต่างๆ โดยใช้ทัชแพด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 3845fcd314a4..76375a363342 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Pag-share ng Audio"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"I-tap para lumipat o magbahagi ng audio"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Sinusuportahan ang pag-share ng audio"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Mga widget ng lock screen"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para magbukas ng app gamit ang isang widget, kakailanganin mong i-verify na ikaw iyan. Bukod pa rito, tandaang puwedeng tingnan ng kahit na sino ang mga ito, kahit na naka-lock ang iyong tablet. Posibleng hindi para sa iyong lock screen ang ilang widget at posibleng hindi ligtas ang mga ito na idagdag dito."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Mga Widget"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Makikita sa itaas ng mga notification ng pag-uusap at bilang larawan sa profile sa lock screen, lumalabas bilang bubble, naaabala ang Huwag Istorbohin"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Priyoridad"</string>
<string name="no_shortcut" msgid="8257177117568230126">"Hindi sinusuportahan ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang mga feature ng pag-uusap"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Magbigay ng Feedback sa Bundle"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Hindi puwedeng baguhin ang mga notification na ito."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Hindi mabago ang mga notification ng tawag."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Hindi mako-configure dito ang pangkat na ito ng mga notification"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Lumipat sa app sa kaliwa o itaas habang ginagamit ang split screen"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Habang nasa split screen: magpalit-palit ng app"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Ilipat ang aktibong window sa pagitan ng mga display"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Ilipat ang window sa kaliwa"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Ilipat ang window sa kanan"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"I-maximize ang window"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"I-minimize ang window"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Lumipat sa susunod na wika"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Lumipat sa dating wika"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gumamit ng mas kaunti sa <xliff:g id="LENGTH">%1$d</xliff:g> (na) character"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero ng build"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Nakopya sa clipboard ang numero ng build."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopyahin sa clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Buksan ang pag-uusap"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Mga widget ng pag-uusap"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Mag-tap sa isang pag-uusap para idagdag ito sa iyong Home screen"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para sa mas mataas na resolution, i-flip ang telepono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Na-on ang screen sa harap"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"naka-fold"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"hindi naka-fold"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Mga kontrol ng system"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Mga system app"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Pag-multitask"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Mga kamakailang app"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Split screen"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Mga shortcut ng app"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Mga keyboard shortcut"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"I-customize ang mga keyboard shortcut"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pindutin ang key para magtalaga ng shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alisin ang shortcut?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pindutin ang key para magtalaga ng shortcut"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Permanente nitong ide-delete ang iyong custom na shortcut."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Mga shortcut ng paghahanap"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Walang resulta ng paghahanap"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"I-collapse ang icon"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Icon ng Action o Meta key"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Icon na plus"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"I-customize"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Tapos na"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"I-expand ang icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handle sa pag-drag"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Mga Setting ng Keyboard"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Magtakda ng shortcut"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Alisin"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Kanselahin"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pindutin ang key"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang key."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang key."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Hindi maitakda ang shortcut."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Mag-navigate gamit ang iyong keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Matuto ng mga keyboard shortcut"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Mag-navigate gamit ang iyong touchpad"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 45d8b4605c29..e1484e3eef4d 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -113,7 +113,7 @@
<string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir uygulamayı kaydet"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tüm ekranı kaydet"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"Tüm ekranı kaydet: %s"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Tüm ekranınızı kaydettiğinizde ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Ekranın tamamını kaydederken ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Bir uygulamayı kaydettiğinizde o uygulamada gösterilen veya oynatılan her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı kaydet"</string>
<string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Kaydedilecek uygulamayı seçin"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ses Paylaşımı"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Geçiş yapmak veya ses paylaşmak için dokunun"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ses paylaşımını destekler"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Kilit ekranı widget\'ları"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Widget kullanarak bir uygulamayı açmak için kimliğinizi doğrulamanız gerekir. Ayrıca, tabletiniz kilitliyken bile widget\'ların herkes tarafından görüntülenebileceğini unutmayın. Bazı widget\'lar kilit ekranınız için tasarlanmamış olabileceğinden buraya eklenmeleri güvenli olmayabilir."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Anladım"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget\'lar"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Görüşme bildirimlerinin üstünde ve kilit ekranında profil resmi olarak gösterilir, baloncuk olarak görünür, Rahatsız Etmeyin\'i kesintiye uğratır"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Öncelikli"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>, sohbet özelliklerini desteklemiyor"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Paketle İlgili Geri Bildirim Verin"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirimler değiştirilemez."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Arama bildirimleri değiştirilemez."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Bu bildirim grubu burada yapılandırılamaz"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran kullanırken soldaki veya üstteki uygulamaya geçiş yapın"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran etkinken: Bir uygulamayı başkasıyla değiştir"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Etkin pencereyi ekranlar arasında taşıma"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Pencereyi sola taşı"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Pencereyi sağa taşı"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Pencereyi ekranı kaplayacak şekilde büyüt"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Pencereyi simge durumuna küçült"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Giriş"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Sonraki dile geç"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Önceki dile geç"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"En fazla <xliff:g id="LENGTH">%1$d</xliff:g> karakter kullanın"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Derleme numarası"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Derleme numarası panoya kopyalandı."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"panoya kopyalayın."</string>
<string name="basic_status" msgid="2315371112182658176">"Görüşmeyi aç"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Görüşme widget\'ları"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Ana ekranınıza eklemek için bir görüşmeye dokunun"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksek çözünürlük için telefonu çevirin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ön ekran açıldı"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"katlanmış"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"katlanmamış"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Sistem kontrolleri"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Sistem uygulamaları"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Çoklu görev"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Son uygulamalar"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Bölünmüş ekran"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Giriş"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Uygulama kısayolları"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erişilebilirlik"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Klavye kısayolları"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klavye kısayollarını özelleştirin"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Kısayol atamak için tuşa basın"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kısayol kaldırılsın mı?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Kısayol atamak için tuşa basın"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bu işlem, özel kısayolunuzu kalıcı olarak siler."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Arama kısayolları"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Arama sonucu yok"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Daralt simgesi"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"İşlem veya Meta tuşu simgesi"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Artı simgesi"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Özelleştir"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Bitti"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Genişlet simgesi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"veya"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"artı"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"eğik çizgi"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sürükleme tutamacı"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klavye Ayarları"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Kısayol ayarla"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Kaldır"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"İptal"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tuşa basın"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tuş kombinasyonu zaten kullanılıyor. Başka bir tuş deneyin."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tuş kombinasyonu zaten kullanılıyor. Başka bir tuş deneyin."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kısayol ayarlanamıyor."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klavyenizi kullanarak gezinin"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klavye kısayollarını öğrenin"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Dokunmatik alanınızı kullanarak gezinin"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 4c4c80ea31a3..e4cc156cd454 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Надсилання аудіо"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Натисніть, щоб змінити режим або надіслати аудіо"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Підтримує надсилання аудіо"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Збережено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Віджети для заблокованого екрана"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Щоб відкрити додаток за допомогою віджета, вам потрібно буде підтвердити особу. Пам’ятайте також, що бачити віджети можуть усі, навіть коли планшет заблоковано. Можливо, деякі віджети не призначені для заблокованого екрана, і додавати їх на нього може бути небезпечно."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Віджети"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"З’являється вгорі сповіщень про розмови і як зображення профілю на заблокованому екрані, відображається як спливаючий чат, перериває режим \"Не турбувати\""</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Пріоритет"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не підтримує функції розмов"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Надіслати груповий відгук"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ці сповіщення не можна змінити."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Сповіщення про виклик не можна змінити."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Цю групу сповіщень не можна налаштувати тут"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Під час розділення екрана перемикатися на додаток ліворуч або вгорі"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Під час розділення екрана: замінити додаток іншим"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Перемістити активне вікно між дисплеями"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Перемістити вікно ліворуч"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Перемістити вікно праворуч"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Розгорнути вікно"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Згорнути вікно"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Метод введення"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Вибрати наступну мову"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Вибрати попередню мову"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Кількість символів має бути менше ніж <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер складання"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Номер складання скопійовано в буфер обміну."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копіювати в буфер обміну"</string>
<string name="basic_status" msgid="2315371112182658176">"Відкрита розмова"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Віджети розмов"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Натисніть розмову, щоб додати її на головний екран"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Для вищої роздільної здатності переверніть телефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Передній екран увімкнено"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"складений"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"розкладений"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Елементи керування системою"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системні додатки"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Багатозадачність"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Нещодавні додатки"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Розділити екран"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Введення"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Комбінації клавіш для додатків"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Доступність"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Комбінації клавіш"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Налаштуйте комбінації клавіш"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Натисніть клавішу, щоб призначити комбінацію клавіш"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Видалити комбінацію клавіш?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натисніть клавішу, щоб призначити комбінацію клавіш"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Вашу власну комбінацію клавіш буде видалено назавжди."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Комбінації клавіш для пошуку"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нічого не знайдено"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок згортання"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Значок клавіші дії або метаклавіші"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Значок \"плюс\""</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Налаштувати"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок розгортання"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"плюс"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"скісна риска"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер переміщення"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налаштування клавіатури"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Налаштувати комбінацію клавіш"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Видалити"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Скасувати"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Натисніть клавішу"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбінація клавіш уже використовується. Спробуйте іншу клавішу."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбінація клавіш уже використовується. Спробуйте іншу клавішу."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не вдалося встановити комбінацію клавіш."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігація за допомогою клавіатури"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Дізнайтеся більше про комбінації клавіш"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігація за допомогою сенсорної панелі"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 109f43eb5632..def880692411 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"بلوٹوتھ استعمال کریں"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"منسلک ہے"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"آڈیو کا اشتراک"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"آڈیو پر سوئچ کرنے یا اس کا اشتراک کرنے کے لیے تھپتھپائیں"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"آڈیو کے اشتراک کو سپورٹ کرتا ہے"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ ہے"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"مقفل اسکرین کے ویجیٹس"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ویجیٹ کے ذریعے ایپ کھولنے کے لیے آپ کو تصدیق کرنی ہوگی کہ یہ آپ ہی ہیں۔ نیز، ذہن میں رکھیں کہ کوئی بھی انہیں دیکھ سکتا ہے، یہاں تک کہ جب آپ کا ٹیبلیٹ مقفل ہو۔ ہو سکتا ہے کچھ ویجٹس آپ کی لاک اسکرین کے لیے نہ بنائے گئے ہوں اور یہاں شامل کرنا غیر محفوظ ہو سکتا ہے۔"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"سمجھ آ گئی"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ویجیٹس"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"یہ گفتگو کی اطلاعات کے اوپری حصّے پر اور مقفل اسکرین پر پروفائل کی تصویر کے بطور دکھائی دیتا ہے، بلبلے کے بطور ظاہر ہوتا ہے، \'ڈسٹرب نہ کریں\' میں مداخلت کرتا ہے"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"ترجیح"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ گفتگو کی خصوصیات کو سپورٹ نہیں کرتی ہے"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"بنڈل کے تاثرات فراہم کریں"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"ان اطلاعات کی ترمیم نہیں کی جا سکتی۔"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"کال کی اطلاعات میں ترمیم نہیں کی جا سکتی۔"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"اطلاعات کے اس گروپ کو یہاں کنفیگر نہیں کیا جا سکتا"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"اسپلٹ اسکرین کا استعمال کرتے ہوئے بائیں یا اوپر ایپ پر سوئچ کریں"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"اسپلٹ اسکرین کے دوران: ایک ایپ کو دوسرے سے تبدیل کریں"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"فعال ونڈو کو ڈسپلیز کے مابین منتقل کریں"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ونڈو کو دائیں طرف منتقل کریں"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ونڈو کو بائیں طرف منتقل کریں"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ونڈو کو بڑا کریں"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ونڈو کو چھوٹا کریں"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ان پٹ"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"اگلی زبان پر سوئچ کریں"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"پچھلی زبان پر سوئچ کریں"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> حروف سے کم استعمال کریں"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"بلڈ نمبر"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"بلڈ نمبر کلپ بورڈ میں کاپی ہو گیا۔"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"کلپ بورڈ میں کاپی کریں۔"</string>
<string name="basic_status" msgid="2315371112182658176">"گفتگو کھولیں"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"گفتگو ویجیٹس"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"اسے اپنی ہوم اسکرین پر شامل کرنے کے لیے گفتگو پر تھپتھپائیں"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"زیادہ ریزولوشن کے لیے، فون پلٹائیں"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"فولڈ ہونے والے آلے کو کھولا جا رہا ہے"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"فرنٹ اسکرین آن ہے"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"فولڈ کردہ"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"اَن فولڈ کردہ"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"سسٹم کنٹرولز"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"سسٹم ایپس"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ملٹی ٹاسکنگ"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"حالیہ ایپس"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"اسپلٹ اسکرین"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"ان پٹ"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"ایپ شارٹ کٹس"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ایکسیسبیلٹی"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"کی بورڈ شارٹ کٹس"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"کی بورڈ شارٹ کٹس کو حسب ضرورت بنائیں"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"شارٹ کٹ تفویض کرنے کے لیے کلید کو دبائیں"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"شارٹ کٹ ہٹائیں؟"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"شارٹ کٹ تفویض کرنے کے لیے کلید کو دبائیں"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"یہ آپ کا حسب ضرورت شارٹ کٹ مستقل طور پر حذف کر دے گا۔"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"تلاش کے شارٹ کٹس"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"تلاش کا کوئی نتیجہ نہیں ہے"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"آئیکن سکیڑیں"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"‏کارروائی یا Meta کلید کا آئیکن"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"پلس کا آئیکن"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"حسب ضرورت بنائیں"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ہو گیا"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"آئیکن پھیلائیں"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"پلس"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"فارورڈ سلیش"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"گھسیٹنے کا ہینڈل"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"کی بورڈ کی ترتیبات"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"شارٹ کٹ سیٹ کریں"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ہٹائیں"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"منسوخ کریں"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"کلید کو دبائیں"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسری کلید آزمائیں۔"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسری کلید آزمائیں۔"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"شارٹ کٹ سیٹ نہیں کیا جا سکتا۔"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"اپنے کی بورڈ کا استعمال کر کے نیویگیٹ کریں"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"کی بورڈ شارٹ کٹس جانیں"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"اپنے ٹچ پیڈ کا استعمال کر کے نیویگیٹ کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 1c27a0055cce..9dab9e1a8d51 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulangan"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio ulashuvi"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Audioni almashtirish yoki ulashish uchun bosing"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audio ulashishi mumkin"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Ekran qulfi vidjetlari"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ilovani vidjet orqali ochish uchun shaxsingizni tasdiqlashingiz kerak. Shuningdek, planshet qulflanganda ham bu axborotlar hammaga koʻrinishini unutmang. Ayrim vidjetlar ekran qulfiga moslanmagan va ularni bu yerda chiqarish xavfli boʻlishi mumkin."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidjetlar"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Suhbat bildirishnomalari tepasida va ekran qulfida profil rasmi sifatida chiqariladi, bulutcha sifatida chiqadi, Bezovta qilinmasin rejimini bekor qiladi"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Muhim"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasida suhbat funksiyalari ishlamaydi"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Jamlanma fikr-mulohaza bildirish"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirishnomalarni tahrirlash imkonsiz."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Chaqiruv bildirishnomalarini tahrirlash imkonsiz."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Ushbu bildirishnomalar guruhi bu yerda sozlanmaydi"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ajratilgan ekranda chapdagi yoki yuqoridagi ilovaga almashish"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ajratilgan rejimda ilovalarni oʻzaro almashtirish"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Faol oynani ekranlararo koʻchirish"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Oynani chapga surish"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Oynani oʻngga surish"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Oynani yoyish"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Oynani kichraytirish"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Kiritish"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Keyingi tilga almashtirish"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Avvalgi tilga almashtirish"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Kiritiladigan belgilar <xliff:g id="LENGTH">%1$d</xliff:g> tadan oshmasin"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nashr raqami"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Nashr raqami vaqtinchalik xotiraga nusxalandi."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"vaqtincha xotiraga nusxalash"</string>
<string name="basic_status" msgid="2315371112182658176">"Suhbatni ochish"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Suhbat vidjetlari"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Bosh ekranga chiqariladigan suhbat ustiga bosing"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Yuqori aniqlik uchun telefonni aylantiring"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Old ekran yoqildi"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"buklangan"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"buklanmagan"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Tizim boshqaruvi tugmalari"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Tizim ilovalari"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multi-vazifalilik"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Oxirgi ilovalar"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ekranni ikkiga ajratish"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Kiritish"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Ilova yorliqlari"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qulayliklar"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Tezkor tugmalar"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tezkor tugmalarni moslash"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tezkor tugma sozlash uchun tugmani bosing"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tezkor tugma olib tashlansinmi?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tezkor tugma sozlash uchun tugmani bosing"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bunda maxsus tezkor tugma butunlay oʻchirib tashlanadi."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tezkor tugmalar qidiruvi"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hech narsa topilmadi"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Yigʻish belgisi"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Amal bajarish uchun Meta tugmasi belgisi"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Plus belgisi"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Moslash"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Tayyor"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Yoyish belgisi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"yoki"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"oldinga qiya chiziq"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Surish dastagi"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatura sozlamalari"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tezkor tugma sozlash"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Olib tashlash"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Bekor qilish"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tugmani bosing"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Bu tugmalar birikmasi band. Boshqasini ishlating."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Bu tugmalar birikmasi band. Boshqasini ishlating."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Buyruq sozlanmadi."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviatura yordamida kezing"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tezkor tugmalar haqida"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Sensorli panel yordamida kezing"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d8e3bc0c1ffd..c709a31b57de 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Chia sẻ âm thanh"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Nhấn để chuyển hoặc chia sẻ âm thanh"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Hỗ trợ tính năng chia sẻ âm thanh"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Tiện ích trên màn hình khoá"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Để dùng tiện ích mở một ứng dụng, bạn cần xác minh danh tính của mình. Ngoài ra, hãy lưu ý rằng bất kỳ ai cũng có thể xem các tiện ích này, ngay cả khi máy tính bảng của bạn được khoá. Một số tiện ích có thể không dành cho màn hình khoá và không an toàn khi thêm vào đây."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Tôi hiểu"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Tiện ích"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
@@ -649,7 +654,7 @@
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Lớp phủ phụ đề"</string>
<string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"bật"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"tắt"</string>
- <string name="sound_settings" msgid="8874581353127418308">"Âm thanh và chế độ rung"</string>
+ <string name="sound_settings" msgid="8874581353127418308">"Âm thanh và rung"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Cài đặt"</string>
<string name="volume_panel_captioning_title" msgid="5984936949147684357">"Phụ đề trực tiếp"</string>
<string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Âm lượng đã giảm xuống mức an toàn hơn"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Hiện ở đầu phần thông báo cuộc trò chuyện và ở dạng ảnh hồ sơ trên màn hình khóa, xuất hiện ở dạng bong bóng, làm gián đoạn chế độ Không làm phiền"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Mức độ ưu tiên"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> không hỗ trợ các tính năng trò chuyện"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Phản hồi về gói"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Không thể sửa đổi các thông báo này."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Không thể sửa đổi các thông báo cuộc gọi."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Không thể định cấu hình nhóm thông báo này tại đây"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Chuyển sang ứng dụng bên trái hoặc ở trên khi đang chia đôi màn hình"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Trong chế độ chia đôi màn hình: thay một ứng dụng bằng ứng dụng khác"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Di chuyển cửa sổ đang hoạt động giữa các màn hình"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Di chuyển cửa sổ sang trái"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Di chuyển cửa sổ sang phải"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Phóng to cửa sổ"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Thu nhỏ cửa sổ"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Đầu vào"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Chuyển sang ngôn ngữ tiếp theo"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Chuyển về ngôn ngữ trước"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Hãy dùng ít hơn <xliff:g id="LENGTH">%1$d</xliff:g> ký tự"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Số bản dựng"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Đã sao chép số bản dựng vào bảng nhớ tạm."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"sao chép vào bảng nhớ tạm."</string>
<string name="basic_status" msgid="2315371112182658176">"Mở cuộc trò chuyện"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Tiện ích trò chuyện"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Nhấn vào một cuộc trò chuyện để thêm cuộc trò chuyện đó vào Màn hình chính"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Để có độ phân giải cao hơn, hãy lật điện thoại"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Đã bật màn hình trước"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gập"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"mở"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Điều khiển hệ thống"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Ứng dụng hệ thống"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Đa nhiệm"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Ứng dụng gần đây"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Chia đôi màn hình"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Phương thức nhập"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Lối tắt ứng dụng"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hỗ trợ tiếp cận"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Phím tắt"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tuỳ chỉnh phím tắt"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nhấn phím để chỉ định phím tắt"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Xoá lối tắt?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nhấn phím để chỉ định lối tắt"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Thao tác này sẽ xoá vĩnh viễn lối tắt tuỳ chỉnh của bạn."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tìm lối tắt"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Không có kết quả tìm kiếm nào"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Biểu tượng Thu gọn"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Biểu tượng phím Meta (phím hành động)"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Biểu tượng dấu cộng"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Tuỳ chỉnh"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Xong"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Biểu tượng Mở rộng"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"hoặc"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"dấu cộng"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"dấu gạch chéo lên"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Nút kéo"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Cài đặt bàn phím"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Đặt phím tắt"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Xoá"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Huỷ"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nhấn phím"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tổ hợp phím đã được sử dụng. Hãy thử một phím khác."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tổ hợp phím đã được sử dụng. Hãy thử một phím khác."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Không đặt được lối tắt."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Di chuyển bằng bàn phím"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tìm hiểu về phím tắt"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Di chuyển bằng bàn di chuột"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d9a974aad94a..a10650716c00 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -113,7 +113,7 @@
<string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"录制单个应用"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"录制整个屏幕"</string>
<string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"全屏录制:%s"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款详情、消息、照片、音频、视频等敏感信息。"</string>
<string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"录制单个应用时,该应用中显示或播放的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
<string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"录制屏幕"</string>
<string name="screenrecord_app_selector_title" msgid="3854492366333954736">"选择要录制的应用"</string>
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"启用蓝牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已连接"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音频分享"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"点按即可切换或分享音频"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支持音频分享"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已保存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"锁屏微件"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"若要使用微件打开应用,您需要验证是您本人在操作。另外请注意,任何人都可以查看此类微件,即使您的平板电脑已锁定。有些微件可能不适合显示在锁定的屏幕中,因此添加到这里可能不安全。"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"知道了"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"微件"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以气泡形式显示在对话通知顶部(屏幕锁定时显示为个人资料照片),并且会中断勿扰模式"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"优先"</string>
<string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>不支持对话功能"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供有关套装的反馈"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"无法修改这些通知。"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"无法修改来电通知。"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"您无法在此处配置这组通知"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分屏模式时,切换到左侧或上方的应用"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"在分屏期间:将一个应用替换为另一个应用"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在各个显示屏之间移动活动窗口"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"将窗口移至左侧"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"将窗口移至右侧"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"最大化窗口"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"最小化窗口"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"输入"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"切换到下一种语言"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"切换到上一种语言"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"必须少于 <xliff:g id="LENGTH">%1$d</xliff:g> 个字符"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build 号"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"已将 Build 号复制到剪贴板。"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"复制到剪贴板。"</string>
<string name="basic_status" msgid="2315371112182658176">"开放式对话"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"对话微件"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"点按对话即可将其添加到主屏幕"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"若要获得更高的分辨率,请翻转手机"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"前屏已开启"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折叠状态"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"展开状态"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"系统控件"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系统应用"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多任务处理"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"最近用过的应用"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分屏"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"输入"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"应用快捷键"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"无障碍功能"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"键盘快捷键"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自定义键盘快捷键"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按下按键即可指定快捷键"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快捷键吗?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按键即可指定快捷键"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"此操作会永久删除您的自定义快捷键。"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜索快捷键"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"无搜索结果"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收起图标"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"操作键或元键图标"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"加号图标"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"自定义"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展开图标"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"加号"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"正斜线"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖动手柄"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"键盘设置"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"设置快捷键"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按下按键"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"按键组合已被使用,请尝试使用其他按键。"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"按键组合已被使用,请尝试使用其他按键。"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"无法设置快捷方式。"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用键盘导航"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"了解键盘快捷键"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用触控板导航"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 443b8556fe18..afbd4118f6ca 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連接"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音訊分享功能"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"輕按即可切換或分享音訊"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支援音訊分享功能"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string>
@@ -449,8 +449,8 @@
<string name="zen_modes_dialog_title" msgid="8854640808100096934">"模式"</string>
<string name="zen_modes_dialog_done" msgid="6654130880256438950">"完成"</string>
<string name="zen_modes_dialog_settings" msgid="2310248023728936697">"設定"</string>
- <string name="zen_mode_on" msgid="9085304934016242591">"開啟"</string>
- <string name="zen_mode_on_with_details" msgid="7416143430557895497">"開 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
+ <string name="zen_mode_on" msgid="9085304934016242591">"已開啟"</string>
+ <string name="zen_mode_on_with_details" msgid="7416143430557895497">"已開啟 • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="zen_mode_off" msgid="1736604456618147306">"關閉"</string>
<string name="zen_mode_set_up" msgid="8231201163894922821">"未設定"</string>
<string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"在「設定」中管理"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"上鎖畫面小工具"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"如要使用小工具開啟應用程式,系統會要求你驗證身分。請注意,所有人都能查看小工具,即使平板電腦已鎖定亦然。部分小工具可能不適用於上鎖畫面,新增至這裡可能會有安全疑慮。"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"知道了"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"小工具"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以對話氣泡形式顯示在對話通知頂部 (在上鎖畫面會顯示為個人檔案相片),並會中斷「請勿打擾」模式"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
<string name="no_shortcut" msgid="8257177117568230126">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」不支援對話功能"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供套裝意見"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"無法修改這些通知。"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"無法修改通話通知。"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"無法在此設定這組通知"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割螢幕時,切換至左邊或上方的應用程式"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割螢幕期間:更換應用程式"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在不同畫面間移動使用中的視窗"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"將視窗移到左邊"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"將視窗移到右邊"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"將視窗放到最大"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"將視窗縮到最小"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"輸入"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"切換至下一個語言"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"切換至上一個語言"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"請使用少於 <xliff:g id="LENGTH">%1$d</xliff:g> 個字元"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"版本號碼已複製到剪貼簿。"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"複製去剪貼簿"</string>
<string name="basic_status" msgid="2315371112182658176">"開啟對話"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"對話小工具"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"輕按對話即可新增至主畫面"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解像度,請切換至手機後置鏡頭"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"正面螢幕已開啟"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已打開"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"系統控制項"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系統應用程式"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多工處理"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"最近使用的應用程式"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割螢幕"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"輸入"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"應用程式捷徑"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙功能"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自訂鍵盤快速鍵"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按鍵即可指派快速鍵"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按鍵即可指派快速鍵"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這將永久刪除你的自訂快速鍵。"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"沒有相符的搜尋結果"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"快捷操作鍵或修飾鍵圖示"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"加號圖示"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"自訂"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"加"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"正斜線"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"鍵盤設定"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"設定快速鍵"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按鍵"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"此按鍵組合已在使用,請改用其他按鍵。"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"此按鍵組合已在使用,請改用其他按鍵。"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定快速鍵。"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤導覽"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"瞭解鍵盤快速鍵"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板導覽"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 4b84f25fca1b..03d70e145068 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連線"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音訊分享"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"輕觸即可切換或分享音訊"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支援音訊分享"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"螢幕鎖定小工具"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"如要使用小工具開啟應用程式,需先驗證身分。請留意,即使平板電腦已鎖定,所有人都還是能查看小工具。某些小工具可能不適用於螢幕鎖定畫面,新增到此可能會有安全疑慮。"</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"我知道了"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"小工具"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會刪除。"</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以對話框的形式顯示在對話通知頂端 (螢幕鎖定時會顯示為個人資料相片),並會中斷「零打擾」模式"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
<string name="no_shortcut" msgid="8257177117568230126">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」不支援對話功能"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供套裝組合意見"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"無法修改這些通知。"</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"無法修改來電通知。"</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"無法在這裡設定這個通知群組"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割畫面時,切換到左邊或上方的應用程式"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割畫面期間:更換應用程式"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在不同畫面間移動使用中的視窗"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"將視窗移至左側"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"將視窗移至右側"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"將視窗放到最大"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"將視窗縮到最小"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"輸入"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"切換到下一個語言"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"切換到上一個語言"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"不得超過 <xliff:g id="LENGTH">%1$d</xliff:g> 個半形字元"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"已將版本號碼複製到剪貼簿。"</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"複製到剪貼簿。"</string>
<string name="basic_status" msgid="2315371112182658176">"開放式對話"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"對話小工具"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"輕觸對話即可新增至主畫面"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解析度,請切換至手機後置鏡頭"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"正面螢幕已開啟"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已展開"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"系統控制選項"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系統應用程式"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多工處理"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"最近使用的應用程式"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割畫面"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"輸入"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"應用程式捷徑"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自訂鍵盤快速鍵"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按下按鍵即可指派快速鍵"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按鍵即可指派快速鍵"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這項操作會永久刪除自訂快速鍵。"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"找不到相符的搜尋結果"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"快捷操作鍵或修飾鍵圖示"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"加號圖示"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"自訂"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"加"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"斜線"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"鍵盤設定"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"設定快速鍵"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按下按鍵"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"這個按鍵組合已在使用中,請改用其他按鍵。"</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"這個按鍵組合已在使用中,請改用其他按鍵。"</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定捷徑。"</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤操作"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"學習鍵盤快速鍵"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板操作"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 4afb67d2af8f..b39c3e9a625b 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -306,7 +306,7 @@
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa iBluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string>
<string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ukwabelana Ngokuqoshiwe"</string>
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Thepha ukuze ushintshe noma wabelane ngokulalelwayo"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Isekela ukwabelana ngokuqoshiwe"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
@@ -528,6 +528,11 @@
<string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Amawijethi wesikrini esikhiyiwe"</string>
<string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ukuze uvule i-app usebenzisa iwijethi, uzodinga ukuqinisekisa ukuthi nguwe. Futhi, khumbula ukuthi noma ubani angakwazi ukuzibuka, nanoma ithebhulethi yakho ikhiyiwe. Amanye amawijethi kungenzeka abengahloselwe ukukhiya isikrini sakho futhi kungenzeka awaphephile ukuthi angafakwa lapha."</string>
<string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ngiyezwa"</string>
+ <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Amawijethi"</string>
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_disabled_text (599170482297578735) -->
+ <skip />
+ <!-- no translation found for glanceable_hub_lockscreen_affordance_action_button_label (7636151133344609375) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
@@ -780,6 +785,7 @@
<string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ivela phezu kwezaziso zengxoxo futhi njengesithombe sephrofayela esikrinini sokukhiya, ivela njengebhamuza, ukuphazamisa okuthi Ungaphazamisi"</string>
<string name="notification_priority_title" msgid="2079708866333537093">"Okubalulekile"</string>
<string name="no_shortcut" msgid="8257177117568230126">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayisekeli izici zengxoxo"</string>
+ <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Nikeza Impendulo Yenqwaba"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Lezi zaziso azikwazi ukushintshwa."</string>
<string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Izaziso zekholi azikwazi ukushintshwa."</string>
<string name="notification_multichannel_desc" msgid="7414593090056236179">"Leli qembu lezaziso alikwazi ukulungiselelwa lapha"</string>
@@ -872,6 +878,10 @@
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Shintshela ku-app ngakwesokunxele noma ngaphezulu ngenkathi usebenzisa ukuhlukanisa isikrini"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"Ngesikhathi sokuhlukaniswa kwesikrini: shintsha i-app ngenye"</string>
<string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Hambisa iwindi elisebenzayo phakathi kwezibonisi"</string>
+ <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Hambisa iwindi liye kwesokudla"</string>
+ <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Hambisa iwindi liye kwesokudla"</string>
+ <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Khulisa iwindi"</string>
+ <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Nciphisa iwindi"</string>
<string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Okokufaka"</string>
<string name="input_switch_input_language_next" msgid="3782155659868227855">"Shintshela olimini olulandelayo"</string>
<string name="input_switch_input_language_previous" msgid="6043341362202336623">"Shintshela olimini lwangaphambili"</string>
@@ -1220,8 +1230,7 @@
<string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Sebenzisa isinhlamvu ezimbalwa kuneziyi-<xliff:g id="LENGTH">%1$d</xliff:g>"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Yakha inombolo"</string>
<string name="build_number_copy_toast" msgid="877720921605503046">"Yakha inombolo ekopishelwe kubhodi yokunamathisela."</string>
- <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
- <skip />
+ <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopishela ebhodini lokunamathisela."</string>
<string name="basic_status" msgid="2315371112182658176">"Vula ingxoxo"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Amawijethi wengxoxo"</string>
<string name="select_conversation_text" msgid="3376048251434956013">"Thepha ingxoxo ukuyengeza Kusikrini sakho sasekhaya"</string>
@@ -1357,8 +1366,7 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ukuze uthole ukulungiswa okuphezulu, phendula ifoni"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string>
- <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
- <skip />
+ <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Isikrini sangaphambili sivuliwe"</string>
<string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kugoqiwe"</string>
<string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kuvuliwe"</string>
<string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1410,7 +1418,6 @@
<string name="shortcut_helper_category_system_controls" msgid="3153344561395751020">"Izilawuli zesistimu"</string>
<string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Ama-app esistimu"</string>
<string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Ukwenza imisebenzi eminingi"</string>
- <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Ama-app wakamuva"</string>
<string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Hlukanisa isikrini"</string>
<string name="shortcut_helper_category_input" msgid="8674018654124839566">"Okokufaka"</string>
<string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Izinqamuleli Zohlelo lokusebenza"</string>
@@ -1418,22 +1425,37 @@
<string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ukufinyeleleka"</string>
<string name="shortcut_helper_title" msgid="8567500639300970049">"Izinqamuleli zekhibhodi"</string>
<string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Hlela izinqamuleli zekhibhodi ngendlela oyifisayo"</string>
- <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Cindezela ukhiye ukuze unikeze isinqamuleli"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Susa isinqamuleli?"</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_dialog_title (8131184731313717780) -->
+ <skip />
+ <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Cindezela ukhiye ukuze unikeze isinqamuleli"</string>
+ <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Lokhu kuzosula isinqamuleli sakho somuntu ngamunye unomphela."</string>
+ <!-- no translation found for shortcut_customize_mode_reset_shortcut_description (2081849715634358684) -->
+ <skip />
<string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sesha izinqamuleli"</string>
<string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ayikho imiphumela yosesho"</string>
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Goqa isithonjana"</string>
<string name="shortcut_helper_content_description_meta_key" msgid="3989315044342124818">"Isithonjana sesenzo noma seMeta"</string>
<string name="shortcut_helper_content_description_plus_icon" msgid="6152683734278299020">"Isithonjana sesengezo"</string>
<string name="shortcut_helper_customize_button_text" msgid="3124983502748069338">"Enza ngendlela oyifisayo"</string>
+ <!-- no translation found for shortcut_helper_reset_button_text (2548243844050633472) -->
+ <skip />
<string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Kwenziwe"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Nweba isithonjana"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"noma"</string>
+ <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"hlanganisa"</string>
+ <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"umugqa otshekele phambili"</string>
<string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Hudula isibambi"</string>
<string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Amasethingi Ekhibhodi"</string>
<string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setha isinqamuleli"</string>
+ <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Susa"</string>
+ <!-- no translation found for shortcut_helper_customize_dialog_reset_button_label (7645535254306312685) -->
+ <skip />
<string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Khansela"</string>
<string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Cindezela ukhiye"</string>
- <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama omunye ukhiye."</string>
+ <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama omunye ukhiye."</string>
+ <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Isinqamuleli asikwazi ukusethwa."</string>
+ <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Funa usebenzisa ikhibhodi yakho"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Funda izinqamuleli zamakhibhodi"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Funa usebenzisa iphedi yokuthinta"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 42e909244f84..113f3d2bc35e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -172,6 +172,9 @@
<!-- Minimum display time for a heads up notification, in milliseconds. -->
<integer name="heads_up_notification_minimum_time">2000</integer>
+ <!-- Minimum display time for a heads up notification if throttling is enabled, in milliseconds. -->
+ <integer name="heads_up_notification_minimum_time_with_throttling">500</integer>
+
<!-- Display time for a sticky heads up notification, in milliseconds. -->
<integer name="sticky_heads_up_notification_time">60000</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 411f36b73802..df7adc019a72 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -236,6 +236,12 @@
<!-- The size of a bluetooth indicator icon that displays next to the RSSI status icon. -->
<dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen>
+ <!-- Height of a small notification in the status bar (2025 redesign version) -->
+ <dimen name="notification_2025_header_height">@*android:dimen/notification_2025_header_height</dimen>
+
+ <!-- Height of a small notification in the status bar (2025 redesign version) -->
+ <dimen name="notification_2025_min_height">@*android:dimen/notification_2025_min_height</dimen>
+
<!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen>
@@ -1995,12 +2001,16 @@
<dimen name="backlight_indicator_step_large_radius">28dp</dimen>
<!-- Touchpad gestures tutorial-->
- <!-- This value is in unit of dp/ms
+ <!-- This value is in dp/ms.
TriggerSwipeUpTouchTracker (which is base for gesture tutorial implementation) uses value
of 0.5dp but from manual testing it's too high and doesn't really feel like it's forcing
slowing down. Also for tutorial it should be fine to lean to the side of being more strict
rather than not strict enough and not teaching user the proper gesture as a result.-->
<dimen name="touchpad_recent_apps_gesture_velocity_threshold">0.05dp</dimen>
+ <!-- This value is in dp/ms.
+ As above, it's not tied to system-wide value (defined in launcher's
+ quickstep_fling_threshold_speed) because for tutorial it's fine to be more strict. -->
+ <dimen name="touchpad_home_gesture_velocity_threshold">0.5dp</dimen>
<!-- Normal gesture threshold is system_gestures_distance_threshold but for tutorial we can
exaggerate gesture, which also works much better with live tracking -->
<dimen name="touchpad_tutorial_gestures_distance_threshold">48dp</dimen>
@@ -2086,7 +2096,7 @@
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
<dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
- <dimen name="volume_dialog_button_size">48dp</dimen>
+ <dimen name="volume_dialog_button_size">40dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
@@ -2094,7 +2104,7 @@
<dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
- <dimen name="volume_dialog_ringer_drawer_button_size">40dp</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 731c4ef463fd..658f2c27b4cb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1336,10 +1336,12 @@
<string name="communal_widgets_disclaimer_text">To open an app using a widget, you\u2019ll need to verify it\u2019s you. Also, keep in mind that anyone can view them, even when your tablet\u2019s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here.</string>
<!-- Button for user to verify they understand the information presented. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_button">Got it</string>
- <!-- Lockscreen affordance to open glanceable hub. [CHAR LIMIT=20] -->
+ <!-- Label for a lock screen affordance to show widgets on the lock screen. [CHAR LIMIT=20] -->
<string name="glanceable_hub_lockscreen_affordance_label">Widgets</string>
- <!-- Text explaining that the glanceable hub affordance is disabled. [CHAR LIMIT=NONE] -->
- <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add Widgets on the lock screen as a shortcut, make sure it is enabled in settings.</string>
+ <!-- Text explaining why the lock screen affordance to show widgets on the lockscreen is disabled and how to enable the affordance in settings. [CHAR LIMIT=NONE] -->
+ <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add the \"Widgets\" shortcut, make sure \"Show widgets on lock screen\" is enabled in settings.</string>
+ <!-- Label for a button used to open Settings in order to enable showing widgets on the lock screen. [CHAR LIMIT=NONE] -->
+ <string name="glanceable_hub_lockscreen_affordance_action_button_label">Settings</string>
<!-- Related to user switcher --><skip/>
@@ -1507,6 +1509,9 @@
<!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
<string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
+ <!-- Content description for accessibility: Tapping this button will open notifications settings [CHAR LIMIT=NONE] -->
+ <string name="accessibility_notification_section_header_open_settings">Open notifications settings</string>
+
<!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
<string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
@@ -1810,6 +1815,7 @@
<string name="volume_ringer_change">Tap to change ringer mode</string>
<string name="volume_ringer_mode">ringer mode</string>
+ <string name="volume_ringer_drawer_closed_content_description"><xliff:g id="volume ringer status" example="Ring">%1$s</xliff:g>, tap to change ringer mode </string>
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
<string name="volume_ringer_hint_mute">mute</string>
@@ -2287,6 +2293,14 @@
<string name="system_multitasking_replace">During split screen: replace an app from one to another</string>
<!-- User visible title for the keyboard shortcut that moves a focused task to a next display [CHAR LIMIT=70] -->
<string name="system_multitasking_move_to_next_display">Move active window between displays</string>
+ <!-- User visible title for the keyboard shortcut that snaps a task to the left in desktop mode [CHAR LIMIT=70] -->
+ <string name="system_desktop_mode_snap_left_window">Move window to the left</string>
+ <!-- User visible title for the keyboard shortcut that snaps a task to the right in desktop mode [CHAR LIMIT=70] -->
+ <string name="system_desktop_mode_snap_right_window">Move window to the right</string>
+ <!-- User visible title for the keyboard shortcut that toggles between maximizing and restoring a task's previous bounds in desktop mode [CHAR LIMIT=70] -->
+ <string name="system_desktop_mode_toggle_maximize_window">Maximize window</string>
+ <!-- User visible title for the keyboard shortcut that minimizes a task in desktop mode [CHAR LIMIT=70] -->
+ <string name="system_desktop_mode_minimize_window">Minimize window</string>
<!-- User visible title for the input keyboard shortcuts list. [CHAR LIMIT=70] -->
<string name="keyboard_shortcut_group_input">Input</string>
@@ -3728,10 +3742,6 @@
that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
for example "Enter split screen". [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_category_multitasking">Multitasking</string>
- <!-- Title of the keyboard shortcut helper category "Recent apps". The helper is a component
- that shows the user which keyboard shortcuts they can use. The "Recent apps" shortcuts are
- for example "Cycle through recent apps". [CHAR LIMIT=NONE] -->
- <string name="shortcutHelper_category_recent_apps">Recent apps</string>
<!-- Title of the keyboard shortcut helper category "Split screen". The helper is a component
that shows the user which keyboard shortcuts they can use. The "Split screen" shortcuts are
for example "Move current app to left split". [CHAR LIMIT=NONE] -->
@@ -3764,6 +3774,11 @@
The helper is a component that shows the user which keyboard shortcuts they can use. Also
allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_dialog_title">Remove shortcut?</string>
+ <!-- Title at the top of the keyboard shortcut helper reset shortcut dialog. This dialog allows
+ the user to remove all custom shortcuts the user has set, resetting to default shortcuts only.
+ Shortcut helper is a component that shows the user which keyboard shortcuts they can use. Also
+ allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_dialog_title">Reset back to default?</string>
<!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
user what action they need to take in the customization dialog to assign a new custom shortcut.
The shortcut customize dialog allows users to add/remove custom shortcuts
@@ -3774,6 +3789,10 @@
users to add/remove custom shortcuts
[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_description">This will delete your custom shortcut permanently.</string>
+ <!-- Sub title at the top of the reset custom shortcut dialog. Explains to the user that the action
+ they're about to take will remove all custom shortcuts they have set, resetting to default shortcuts only.
+ The shortcut customize dialog allows users to add/remove custom shortcuts [CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_description">This will delete all your custom shortcuts permanently.</string>
<!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3799,6 +3818,9 @@
shortcut helper The helper is a component that shows the user which keyboard shortcuts
they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_customize_button_text">Customize</string>
+ <!-- Description text of the button that allows user to resets all custom shortcuts in keyboard
+ shortcut helper when in customization mode. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_reset_button_text">Reset</string>
<!-- Description text of the button that allows user to exit shortcut customization mode in
keyboard shortcut helper The helper is a component that shows the user which keyboard
shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3816,6 +3838,14 @@
[CHAR LIMIT=NONE]
-->
<string name="shortcut_helper_key_combinations_or_separator">or</string>
+ <!-- Word that combines different possible key combinations of a shortcut. For example the
+ "Go to home screen" shortcut could be triggered using "ctrl" plus "h".
+ This is only used for accessibility content descriptions of the key combinations.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_key_combinations_and_conjunction">plus</string>
+ <!-- Word that represents the forward slash character "/". To be used only in accessibility
+ content descriptions. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_key_combinations_forward_slash">forward slash</string>
<!-- Content description of the drag handle that allows to swipe to dismiss the shortcut helper.
The helper is a component that shows the user which keyboard shortcuts they can
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
@@ -3833,6 +3863,10 @@
confirm and remove previously added custom shortcut. The helper is a component that
shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_customize_dialog_remove_button_label">Remove</string>
+ <!-- Label on the reset shortcut button in keyboard shortcut helper customize dialog, that allows user to
+ confirm and reset all added custom shortcut. The helper is a component that
+ shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_customize_dialog_reset_button_label">Yes, reset</string>
<!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
The helper is a component that shows the user which keyboard shortcuts they can use.
@@ -3919,6 +3953,8 @@
<string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
<string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
+ <!-- Content description for the animation playing during the tutorial. The user can click the animation to pause and unpause playback. [CHAR LIMIT=NONE] -->
+ <string name="tutorial_animation_content_description">Tutorial animation, click to pause and resume play.</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0381811e29ed..9aa71374fb8e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1084,11 +1084,6 @@
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
-
- <!--
- TODO(b/309578419): Make the activity handle insets properly and then remove this.
- -->
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="Widget.SliceView.VolumePanel">
diff --git a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
index 877637e0b0d8..1607121f230f 100644
--- a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
@@ -17,10 +17,10 @@
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
- android:id="@+id/transition"
+ android:id="@+id/close_to_open_transition"
app:constraintSetEnd="@+id/volume_dialog_ringer_drawer_open"
app:constraintSetStart="@+id/volume_dialog_ringer_drawer_close"
- app:transitionEasing="path(0.05f, 0.7f, 0.1f, 1f)"
+ app:transitionEasing="cubic(0.05, 0.7, 0.1, 1.0)"
app:duration="400">
</Transition>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json
new file mode 100644
index 000000000000..c5a83c4d0d8c
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json
@@ -0,0 +1,95 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "a83f96ef4babe730b3a00e8acb777a25",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1, `span_y` INTEGER NOT NULL DEFAULT 3, `span_y_new` INTEGER NOT NULL DEFAULT 1)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ },
+ {
+ "fieldPath": "spanY",
+ "columnName": "span_y",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "3"
+ },
+ {
+ "fieldPath": "spanYNew",
+ "columnName": "span_y_new",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a83f96ef4babe730b3a00e8acb777a25')"
+ ]
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index ab611901328d..fc536bdb126b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -25,9 +25,17 @@ import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
import android.annotation.LongDef;
import android.content.Context;
import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicyConstants;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.policy.ScreenDecorationsUtils;
import java.lang.annotation.Retention;
@@ -39,10 +47,7 @@ import java.util.StringJoiner;
*/
public class QuickStepContract {
- public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
- public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
- // See ISysuiUnlockAnimationController.aidl
- public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
+ private static final String TAG = "QuickStepContract";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
@@ -412,4 +417,20 @@ public class QuickStepContract {
public static boolean supportsRoundedCornersOnWindows(Resources resources) {
return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources);
}
+
+ /**
+ * Adds the provided interface to the bundle using the interface descriptor as the key
+ */
+ public static void addInterface(@Nullable IInterface iInterface, @NonNull Bundle out) {
+ if (iInterface != null) {
+ IBinder binder = iInterface.asBinder();
+ if (binder != null) {
+ try {
+ out.putIBinder(binder.getInterfaceDescriptor(), binder);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Invalid interface description " + binder, e);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index df9f7053c3f3..5af80cbd4b29 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -63,6 +63,7 @@ import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
import com.android.systemui.res.R as SysuiR
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
@@ -95,9 +96,10 @@ constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val batteryController: BatteryController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ // TODO b/362719719 - We should use the configuration controller associated with the display.
private val configurationController: ConfigurationController,
@DisplaySpecific private val resources: Resources,
- private val context: Context,
+ @DisplaySpecific val context: Context,
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
@@ -465,6 +467,15 @@ constructor(
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
zenModeController.addCallback(zenModeCallback)
+ if (SceneContainerFlag.isEnabled) {
+ handleDoze(
+ when (AOD) {
+ keyguardTransitionInteractor.getCurrentState() -> 1f
+ keyguardTransitionInteractor.getStartedState() -> 1f
+ else -> 0f
+ }
+ )
+ }
disposableHandle =
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index add459b84ac1..1083136b570a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -103,7 +104,8 @@ public class KeyguardDisplayManager {
};
@Inject
- public KeyguardDisplayManager(Context context,
+ public KeyguardDisplayManager(
+ @ShadeDisplayAware Context context,
Lazy<NavigationBarController> navigationBarControllerLazy,
DisplayTracker displayTracker,
@Main Executor mainExecutor,
@@ -331,7 +333,8 @@ public class KeyguardDisplayManager {
private boolean mIsInConcurrentDisplayState;
@Inject
- DeviceStateHelper(Context context,
+ DeviceStateHelper(
+ @ShadeDisplayAware Context context,
DeviceStateManager deviceStateManager,
@Main Executor mainExecutor) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 5a02486d5096..07bd813c2420 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -25,6 +25,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -43,7 +44,7 @@ import javax.inject.Inject
class KeyguardUnfoldTransition
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val keyguardRootView: KeyguardRootView,
private val shadeWindowView: NotificationShadeWindowView,
statusBarStateController: StatusBarStateController,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2c8fff83a821..a703b027b691 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -101,6 +101,7 @@ import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -151,6 +152,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
@@ -643,6 +645,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
} else {
data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
}
+ if (data == null) {
+ Log.w(TAG, "Null SimData for subscription: "
+ + changedSubscriptions.get(i));
+ continue;
+ }
for (int j = 0; j < mCallbacks.size(); j++) {
var cb = mCallbacks.get(j).get();
if (cb != null) {
@@ -2157,7 +2164,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
@Inject
protected KeyguardUpdateMonitor(
- Context context,
+ @ShadeDisplayAware Context context,
UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
@@ -3415,6 +3422,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Removes all valid subscription info from the map for the given slotId.
*/
private void invalidateSlot(int slotId) {
+ if (simPinUseSlotId()) {
+ return;
+ }
synchronized (mSimDataLockObject) {
var iter = simPinUseSlotId() ? mSimDatasBySlotId.entrySet().iterator()
: mSimDatas.entrySet().iterator();
@@ -3446,7 +3456,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
updateTelephonyCapable(true);
}
-
invalidateSlot(slotId);
}
@@ -3966,10 +3975,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean refreshSimState(int subId, int slotId) {
int state = mTelephonyManager.getSimState(slotId);
synchronized (mSimDataLockObject) {
- SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
invalidateSlot(slotId);
}
+ SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
final boolean changed;
if (data == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 9513c8e4d8be..5a9cbce73e4b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -174,9 +174,20 @@ public interface KeyguardViewController {
/**
* Stop showing the alternate bouncer, if showing.
+ *
+ * <p>Should be like calling {@link #hideAlternateBouncer(boolean, boolean)} with a {@code true}
+ * {@code clearDismissAction} parameter.
*/
void hideAlternateBouncer(boolean updateScrim);
+ /**
+ * Stop showing the alternate bouncer, if showing.
+ *
+ * @param updateScrim Whether to update the scrim
+ * @param clearDismissAction Whether the pending dismiss action should be cleared
+ */
+ void hideAlternateBouncer(boolean updateScrim, boolean clearDismissAction);
+
// TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
// only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
// achieving complete abstraction away from where the Keyguard View is mounted.
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index fc42045c02c8..0305b5e5ab63 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -30,6 +30,7 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
import com.android.systemui.util.ThreadAssert;
@@ -47,7 +48,7 @@ public abstract class ClockRegistryModule {
@Provides
@SysUISingleton
public static ClockRegistry getClockRegistry(
- @Application Context context,
+ @ShadeDisplayAware Context context,
PluginManager pluginManager,
@Application CoroutineScope scope,
@Main CoroutineDispatcher mainDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 08236b7e280a..ca5424bc0c52 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.systemui;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index a46b236d46fb..981732278acd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -46,6 +46,8 @@ import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.util.NotificationChannels;
+import com.android.wm.shell.dagger.HasWMComponent;
+import com.android.wm.shell.dagger.WMComponent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
@@ -62,7 +64,7 @@ import javax.inject.Provider;
* Application class for SystemUI.
*/
public class SystemUIApplication extends Application implements
- SystemUIAppComponentFactoryBase.ContextInitializer {
+ SystemUIAppComponentFactoryBase.ContextInitializer, HasWMComponent {
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
@@ -490,4 +492,10 @@ public class SystemUIApplication extends Application implements
n.addExtras(extras);
}
+
+ @NonNull
+ @Override
+ public WMComponent getWMComponent() {
+ return mInitializer.getWMComponent();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 5c75a49818a6..f530522fb707 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -24,9 +24,9 @@ import android.util.Log;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
-import com.android.systemui.dagger.WMComponent;
import com.android.systemui.res.R;
import com.android.systemui.util.InitializationChecker;
+import com.android.wm.shell.dagger.WMComponent;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.shared.ShellTransitions;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 3cf400aa5c16..5b433464c1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -18,7 +18,6 @@ package com.android.systemui.accessibility;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
@@ -48,7 +47,6 @@ import androidx.annotation.NonNull;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
@@ -118,15 +116,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
@Override
protected WindowMagnificationController createInstance(Display display) {
final Context windowContext = mContext.createWindowContext(display,
- Flags.createWindowlessWindowMagnifier()
- ? TYPE_ACCESSIBILITY_OVERLAY
- : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
- /* options */ null);
+ TYPE_ACCESSIBILITY_OVERLAY,
+ /* options */ null);
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
Supplier<SurfaceControlViewHost> scvhSupplier = () ->
- Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
- mContext.getDisplay(), new InputTransferToken(), TAG) : null;
+ new SurfaceControlViewHost(mContext,
+ mContext.getDisplay(), new InputTransferToken(), TAG);
return new WindowMagnificationController(
windowContext,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 3794e7bf6b55..08d3e17c03d7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -833,9 +833,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
// Set the surface of the SurfaceView to black to avoid users seeing the contents below the
// magnifier when the mirrored surface has an alpha less than 1.
- if (Flags.addBlackBackgroundForWindowMagnifier()) {
- mTransaction.setColor(mMirrorSurfaceView.getSurfaceControl(), COLOR_BLACK_ARRAY);
- }
+ mTransaction.setColor(mMirrorSurfaceView.getSurfaceControl(), COLOR_BLACK_ARRAY);
mTransaction.show(mMirrorSurface)
.reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
modifyWindowMagnification(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index ffb5f3d47bcc..559e6f767f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -20,7 +20,6 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
-import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
@@ -43,7 +42,6 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
@@ -77,9 +75,6 @@ class MenuInfoRepository {
private final Context mContext;
private final Configuration mConfiguration;
- private final AccessibilityManager mAccessibilityManager;
- private final AccessibilityManager.AccessibilityServicesStateChangeListener
- mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
private final SecureSettings mSecureSettings;
@@ -147,10 +142,9 @@ class MenuInfoRepository {
}
};
- MenuInfoRepository(Context context, AccessibilityManager accessibilityManager,
+ MenuInfoRepository(Context context,
OnSettingsContentsChanged settingsContentsChanged, SecureSettings secureSettings) {
mContext = context;
- mAccessibilityManager = accessibilityManager;
mConfiguration = new Configuration(context.getResources().getConfiguration());
mSettingsContentsCallback = settingsContentsChanged;
mSecureSettings = secureSettings;
@@ -244,13 +238,6 @@ class MenuInfoRepository {
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
UserHandle.USER_CURRENT);
- if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
- mSecureSettings.registerContentObserverForUserSync(
- mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
- /* notifyForDescendants */ false,
- mMenuTargetFeaturesContentObserver,
- UserHandle.USER_CURRENT);
- }
mSecureSettings.registerContentObserverForUserSync(
mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants */ false, mMenuSizeContentObserver,
@@ -264,11 +251,6 @@ class MenuInfoRepository {
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
mContext.registerComponentCallbacks(mComponentCallbacks);
-
- if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
- mAccessibilityManager.addAccessibilityServicesStateChangeListener(
- mA11yServicesStateChangeListener);
- }
}
void unregisterObserversAndCallbacks() {
@@ -276,11 +258,6 @@ class MenuInfoRepository {
mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
mContext.unregisterComponentCallbacks(mComponentCallbacks);
-
- if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
- mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
- mA11yServicesStateChangeListener);
- }
}
interface OnSettingsContentsChanged {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index cb96e7859fba..cfcaa4fea99b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -42,8 +42,7 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
NavigationModeController navigationModeController) {
mWindowManager = viewCaptureAwareWindowManager;
- MenuViewModel menuViewModel = new MenuViewModel(
- context, accessibilityManager, secureSettings);
+ MenuViewModel menuViewModel = new MenuViewModel(context, secureSettings);
MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index f924784a5535..46c407e24fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility.floatingmenu;
import android.content.Context;
-import android.view.accessibility.AccessibilityManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -43,10 +42,9 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
- MenuViewModel(Context context, AccessibilityManager accessibilityManager,
- SecureSettings secureSettings) {
- mInfoRepository = new MenuInfoRepository(context,
- accessibilityManager, /* settingsContentsChanged= */ this, secureSettings);
+ MenuViewModel(Context context, SecureSettings secureSettings) {
+ mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this,
+ secureSettings);
}
@Override
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 1f21af80cebb..ad12229fe4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -55,7 +55,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
@@ -67,7 +66,6 @@ import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
-import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -87,6 +85,7 @@ import java.util.stream.Collectors;
*/
public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
HearingDeviceItemCallback, BluetoothCallback {
+
private static final String TAG = "HearingDevicesDialogDelegate";
@VisibleForTesting
static final String ACTION_BLUETOOTH_DEVICE_DETAILS =
@@ -96,25 +95,27 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@VisibleForTesting
static final Intent LIVE_CAPTION_INTENT = new Intent(
"com.android.settings.action.live_caption");
+
private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final ActivityStarter mActivityStarter;
- private final boolean mShowPairNewDevice;
private final LocalBluetoothManager mLocalBluetoothManager;
private final Handler mMainHandler;
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
- private final HapClientProfile mHapClientProfile;
private final HearingDevicesUiEventLogger mUiEventLogger;
+ private final boolean mShowPairNewDevice;
private final int mLaunchSourceId;
- private HearingDevicesListAdapter mDeviceListAdapter;
- private HearingDevicesPresetsController mPresetsController;
- private Context mApplicationContext;
+
private SystemUIDialog mDialog;
+
private RecyclerView mDeviceList;
private List<DeviceItem> mHearingDeviceItemList;
+ private HearingDevicesListAdapter mDeviceListAdapter;
+
private View mPresetLayout;
private Spinner mPresetSpinner;
+ private HearingDevicesPresetsController mPresetController;
private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
new HearingDevicesPresetsController.PresetCallback() {
@@ -122,20 +123,18 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
int activePresetIndex) {
mMainHandler.post(
- () -> refreshPresetInfoAdapter(presetInfos, activePresetIndex));
+ () -> refreshPresetUi(presetInfos, activePresetIndex));
}
@Override
public void onPresetCommandFailed(int reason) {
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ mPresetController.refreshPresetInfo();
mMainHandler.post(() -> {
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- showPresetErrorToast(mApplicationContext);
+ showErrorToast(R.string.hearing_devices_presets_error);
});
}
};
+
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -159,7 +158,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@AssistedInject
public HearingDevicesDialogDelegate(
- @Application Context applicationContext,
@Assisted boolean showPairNewDevice,
@Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId,
SystemUIDialog.Factory systemUIDialogFactory,
@@ -169,7 +167,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Main Handler handler,
AudioManager audioManager,
HearingDevicesUiEventLogger uiEventLogger) {
- mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -178,7 +175,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mMainHandler = handler;
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
- mHapClientProfile = mProfileManager.getHapClientProfile();
mUiEventLogger = uiEventLogger;
mLaunchSourceId = launchSourceId;
}
@@ -229,38 +225,26 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- CachedBluetoothDevice activeHearingDevice;
- mHearingDeviceItemList = getHearingDevicesList();
- if (mPresetsController != null) {
- activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
- } else {
- activeHearingDevice = null;
+ refreshDeviceUi();
+ if (mPresetController != null) {
+ mPresetController.setDevice(getActiveHearingDevice());
+ mMainHandler.post(() -> {
+ mPresetLayout.setVisibility(
+ mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
+ });
}
- mMainHandler.post(() -> {
- mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
- });
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
@@ -306,13 +290,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
if (mLocalBluetoothManager == null) {
return;
}
-
mLocalBluetoothManager.getEventManager().registerCallback(this);
- if (mPresetsController != null) {
- mPresetsController.registerHapCallback();
- if (mHapClientProfile != null && !mHapClientProfile.isProfileReady()) {
- mProfileManager.addServiceListener(mPresetsController);
- }
+ if (mPresetController != null) {
+ mPresetController.registerHapCallback();
}
}
@@ -322,37 +302,25 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
return;
}
- if (mPresetsController != null) {
- mPresetsController.unregisterHapCallback();
- mProfileManager.removeServiceListener(mPresetsController);
+ if (mPresetController != null) {
+ mPresetController.unregisterHapCallback();
}
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
- @VisibleForTesting
- void setHearingDevicesPresetsController(HearingDevicesPresetsController controller) {
- mPresetsController = controller;
- }
-
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mHearingDeviceItemList = getHearingDevicesList();
+ mHearingDeviceItemList = getHearingDeviceItemList();
mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
mDeviceList.setAdapter(mDeviceListAdapter);
}
private void setupPresetSpinner(SystemUIDialog dialog) {
- if (mPresetsController == null) {
- mPresetsController = new HearingDevicesPresetsController(mProfileManager,
- mPresetCallback);
- }
- final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
- mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
+ mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ mPresetController.setDevice(getActiveHearingDevice());
mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
mPresetSpinner.setAdapter(mPresetInfoAdapter);
-
// disable redundant Touch & Hold accessibility action for Switch Access
mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
@@ -362,20 +330,18 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
super.onInitializeAccessibilityNodeInfo(host, info);
}
});
-
- // Refresh the spinner and setSelection(index, false) before setOnItemSelectedListener() to
- // avoid extra onItemSelected() get called when first register the listener.
- final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ // Should call setSelection(index, false) for the spinner before setOnItemSelectedListener()
+ // to avoid extra onItemSelected() get called when first register the listener.
+ refreshPresetUi(mPresetController.getAllPresetInfo(),
+ mPresetController.getActivePresetIndex());
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mPresetInfoAdapter.setSelected(position);
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
mLaunchSourceId);
- mPresetsController.selectPreset(
- mPresetsController.getAllPresetInfo().get(position).getIndex());
+ mPresetController.selectPreset(
+ mPresetController.getAllPresetInfo().get(position).getIndex());
}
@Override
@@ -383,9 +349,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
// Do nothing
}
});
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+
+ mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
@@ -405,13 +370,12 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
private void setupRelatedToolsView(SystemUIDialog dialog) {
-
final Context context = dialog.getContext();
final List<ToolItem> toolItemList = new ArrayList<>();
final String[] toolNameArray;
final String[] toolIconArray;
- ToolItem preInstalledItem = getLiveCaption(context);
+ ToolItem preInstalledItem = getLiveCaptionToolItem(context);
if (preInstalledItem != null) {
toolItemList.add(preInstalledItem);
}
@@ -432,7 +396,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
for (int i = 0; i < toolItemList.size(); i++) {
- View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+ View view = createToolView(context, toolItemList.get(i), toolsContainer);
toolsContainer.addView(view);
if (i != toolItemList.size() - 1) {
final int spaceSize = context.getResources().getDimensionPixelSize(
@@ -444,8 +408,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
}
- private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos,
- int activePresetIndex) {
+ private void refreshDeviceUi() {
+ mHearingDeviceItemList = getHearingDeviceItemList();
+ mMainHandler.post(() -> {
+ mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ });
+ }
+
+ private void refreshPresetUi(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) {
mPresetInfoAdapter.clear();
mPresetInfoAdapter.addAll(
presetInfos.stream().map(BluetoothHapPresetInfo::getName).toList());
@@ -460,12 +430,11 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
}
- private List<DeviceItem> getHearingDevicesList() {
+ private List<DeviceItem> getHearingDeviceItemList() {
if (mLocalBluetoothManager == null
|| !mLocalBluetoothManager.getBluetoothAdapter().isEnabled()) {
return emptyList();
}
-
return mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy().stream()
.map(this::createHearingDeviceItem)
.filter(Objects::nonNull)
@@ -473,8 +442,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
@Nullable
- private CachedBluetoothDevice getActiveHearingDevice(List<DeviceItem> hearingDeviceItemList) {
- return hearingDeviceItemList.stream()
+ private CachedBluetoothDevice getActiveHearingDevice() {
+ return mHearingDeviceItemList.stream()
.filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
.map(DeviceItem::getCachedBluetoothDevice)
.findFirst()
@@ -495,7 +464,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
@NonNull
- private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+ private View createToolView(Context context, ToolItem item, ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
false);
ImageView icon = view.requireViewById(R.id.tool_icon);
@@ -522,7 +491,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
return view;
}
- private ToolItem getLiveCaption(Context context) {
+ private ToolItem getLiveCaptionToolItem(Context context) {
final PackageManager packageManager = context.getPackageManager();
LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName());
final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
@@ -534,7 +503,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
LIVE_CAPTION_INTENT,
/* isCustomIcon= */ true);
}
-
return null;
}
@@ -544,7 +512,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
}
- private void showPresetErrorToast(Context context) {
- Toast.makeText(context, R.string.hearing_devices_presets_error, Toast.LENGTH_SHORT).show();
+ private void showErrorToast(int stringResId) {
+ Toast.makeText(mDialog.getContext(), stringResId, Toast.LENGTH_SHORT).show();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
index aa95fd038260..e109108d7df5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -25,16 +25,18 @@ import android.bluetooth.BluetoothHapPresetInfo;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.utils.ThreadUtils;
+import java.util.ArrayList;
import java.util.List;
/**
- * The controller of the hearing devices presets of the bluetooth Hearing Access Profile.
+ * The controller of handling hearing device preset with Bluetooth Hearing Access Profile(HAP).
*/
public class HearingDevicesPresetsController implements
LocalBluetoothProfileManager.ServiceListener, BluetoothHapClient.Callback {
@@ -46,11 +48,13 @@ public class HearingDevicesPresetsController implements
private final HapClientProfile mHapClientProfile;
private final PresetCallback mPresetCallback;
- private CachedBluetoothDevice mActiveHearingDevice;
+ private CachedBluetoothDevice mDevice;
+ private List<BluetoothHapPresetInfo> mPresetInfos = new ArrayList<>();
+ private int mActivePresetIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
private int mSelectedPresetIndex;
- public HearingDevicesPresetsController(LocalBluetoothProfileManager profileManager,
- PresetCallback presetCallback) {
+ public HearingDevicesPresetsController(@NonNull LocalBluetoothProfileManager profileManager,
+ @Nullable PresetCallback presetCallback) {
mProfileManager = profileManager;
mHapClientProfile = mProfileManager.getHapClientProfile();
mPresetCallback = presetCallback;
@@ -61,7 +65,7 @@ public class HearingDevicesPresetsController implements
if (mHapClientProfile != null && mHapClientProfile.isProfileReady()) {
mProfileManager.removeServiceListener(this);
registerHapCallback();
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@@ -72,51 +76,53 @@ public class HearingDevicesPresetsController implements
@Override
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ ", presetIndex: " + presetIndex + ", reason: " + reason);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetInfoChanged(@NonNull BluetoothDevice device,
@NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress()
+ ", reason: " + reason + ", infoList: " + presetInfoList);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
selectPresetIndependently(mSelectedPresetIndex);
@@ -125,33 +131,43 @@ public class HearingDevicesPresetsController implements
@Override
public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
}
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
/**
- * Registers a callback to be notified about operation changed for {@link HapClientProfile}.
+ * Registers a callback to be notified about operation changed of {@link HapClientProfile}.
*/
public void registerHapCallback() {
if (mHapClientProfile != null) {
+ if (!mHapClientProfile.isProfileReady()) {
+ mProfileManager.addServiceListener(this);
+ Log.w(TAG, "Profile is not ready yet, the callback will be registered once the "
+ + "profile is ready.");
+ return;
+ }
try {
mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
} catch (IllegalArgumentException e) {
@@ -163,9 +179,10 @@ public class HearingDevicesPresetsController implements
}
/**
- * Removes a previously-added {@link HapClientProfile} callback.
+ * Removes a previously-added {@link HapClientProfile} callback if exist.
*/
public void unregisterHapCallback() {
+ mProfileManager.removeServiceListener(this);
if (mHapClientProfile != null) {
try {
mHapClientProfile.unregisterCallback(this);
@@ -177,108 +194,137 @@ public class HearingDevicesPresetsController implements
}
/**
- * Sets the hearing device for this controller to control the preset if it supports
- * {@link HapClientProfile}.
+ * Sets the device for this controller to control the preset if it supports
+ * {@link HapClientProfile}, otherwise the device of this controller will be {@code null}.
*
- * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
- * and support {@link HapClientProfile}.
+ * @param device the {@link CachedBluetoothDevice} set to the controller
*/
- public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) {
- if (mHapClientProfile == null || activeHearingDevice == null) {
- mActiveHearingDevice = null;
- return;
- }
- if (activeHearingDevice.getProfiles().stream().anyMatch(
+ public void setDevice(@Nullable CachedBluetoothDevice device) {
+ if (device != null && device.getProfiles().stream().anyMatch(
profile -> profile instanceof HapClientProfile)) {
- mActiveHearingDevice = activeHearingDevice;
+ mDevice = device;
} else {
- mActiveHearingDevice = null;
+ mDevice = null;
}
+ refreshPresetInfo();
}
/**
- * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
- * the device group according to whether it supports synchronized presets or not.
+ * Refreshes the preset info of {@code mDevice}. If the preset info list or the active preset
+ * index is updated, the {@link PresetCallback#onPresetInfoUpdated(List, int)} will be called
+ * to notify the change.
*
- * @param presetIndex an index of one of the available presets
+ * <b>Note:</b> If {@code mDevice} is null, the cached preset info and active preset index will
+ * be reset to empty list and {@code BluetoothHapClient.PRESET_INDEX_UNAVAILABLE} respectively.
*/
- public void selectPreset(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
- return;
+ public void refreshPresetInfo() {
+ List<BluetoothHapPresetInfo> updatedInfos = new ArrayList<>();
+ int updatedActiveIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ if (mHapClientProfile != null && mDevice != null) {
+ updatedInfos = mHapClientProfile.getAllPresetInfo(mDevice.getDevice()).stream().filter(
+ BluetoothHapPresetInfo::isAvailable).toList();
+ updatedActiveIndex = mHapClientProfile.getActivePresetIndex(mDevice.getDevice());
}
- mSelectedPresetIndex = presetIndex;
- boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
- mActiveHearingDevice.getDevice());
- int hapGroupId = mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice());
- if (supportSynchronizedPresets) {
- if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- selectPresetSynchronously(hapGroupId, presetIndex);
- } else {
- Log.w(TAG, "supportSynchronizedPresets but hapGroupId is invalid.");
- selectPresetIndependently(presetIndex);
+ final boolean infoUpdated = !mPresetInfos.equals(updatedInfos);
+ final boolean activeIndexUpdated = mActivePresetIndex != updatedActiveIndex;
+ mPresetInfos = updatedInfos;
+ mActivePresetIndex = updatedActiveIndex;
+ if (infoUpdated || activeIndexUpdated) {
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetInfoUpdated(mPresetInfos, mActivePresetIndex);
}
- } else {
- selectPresetIndependently(presetIndex);
}
}
/**
- * Gets all preset info for {@code mActiveHearingDevice} device.
- *
- * @return a list of all known preset info
+ * @return if the preset control is available. The preset control is available only
+ * when the {@code mDevice} supports HAP and the retrieved preset info list is not empty.
+ */
+ public boolean isPresetControlAvailable() {
+ boolean deviceValid = mDevice != null && mDevice.isConnectedHapClientDevice();
+ boolean hasPreset = mPresetInfos != null && !mPresetInfos.isEmpty();
+ return deviceValid && hasPreset;
+ }
+
+ /**
+ * @return a list of {@link BluetoothHapPresetInfo} retrieved from {@code mDevice}
*/
public List<BluetoothHapPresetInfo> getAllPresetInfo() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return emptyList();
}
- return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
- BluetoothHapPresetInfo::isAvailable).toList();
+ return mPresetInfos;
}
/**
- * Gets the currently active preset for {@code mActiveHearingDevice} device.
+ * Gets the currently active preset of {@code mDevice}.
*
* @return active preset index
*/
public int getActivePresetIndex() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
}
- return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
+ return mActivePresetIndex;
+ }
+
+ /**
+ * Selects the preset for {@code mDevice}. Performs individual or group operation according
+ * to whether the device supports synchronized presets feature or not.
+ *
+ * @param presetIndex an index of one of the available presets
+ */
+ public void selectPreset(int presetIndex) {
+ if (mDevice == null || mHapClientProfile == null) {
+ return;
+ }
+ mSelectedPresetIndex = presetIndex;
+ boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
+ mDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mDevice.getDevice());
+ if (supportSynchronizedPresets) {
+ if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ selectPresetSynchronously(hapGroupId, presetIndex);
+ } else {
+ Log.w(TAG, "supportSynchronizedPresets but hapGroupId is invalid.");
+ selectPresetIndependently(presetIndex);
+ }
+ } else {
+ selectPresetIndependently(presetIndex);
+ }
}
private void selectPresetSynchronously(int groupId, int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetSynchronously"
+ ", presetIndex: " + presetIndex
+ ", groupId: " + groupId
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
mHapClientProfile.selectPresetForGroup(groupId, presetIndex);
}
private void selectPresetIndependently(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetIndependently"
+ ", presetIndex: " + presetIndex
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
- mHapClientProfile.selectPreset(mActiveHearingDevice.getDevice(), presetIndex);
- final CachedBluetoothDevice subDevice = mActiveHearingDevice.getSubDevice();
+ mHapClientProfile.selectPreset(mDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mDevice.getSubDevice();
if (subDevice != null) {
if (DEBUG) {
Log.d(TAG, "selectPreset for subDevice, device: " + subDevice);
}
mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
}
- for (final CachedBluetoothDevice memberDevice :
- mActiveHearingDevice.getMemberDevice()) {
+ for (final CachedBluetoothDevice memberDevice : mDevice.getMemberDevice()) {
if (DEBUG) {
Log.d(TAG, "selectPreset for memberDevice, device: " + memberDevice);
}
@@ -294,9 +340,8 @@ public class HearingDevicesPresetsController implements
/**
* Called when preset info from {@link HapClientProfile} operation get updated.
*
- * @param presetInfos all preset info for {@code mActiveHearingDevice} device
- * @param activePresetIndex currently active preset index for {@code mActiveHearingDevice}
- * device
+ * @param presetInfos all preset info of {@code mDevice}
+ * @param activePresetIndex currently active preset index of {@code mDevice}
*/
void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
index d4e74d3bb906..9e1b09cf7891 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
@@ -162,11 +162,12 @@ public class AmbientStatusBarView extends ConstraintLayout {
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
View icon = mStatusIcons.get(iconType);
- if (icon == null) {
- return;
- }
+ if (icon == null) return;
+
if (show && contentDescription != null) {
icon.setContentDescription(contentDescription);
+ icon.setFocusable(true);
+ icon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
icon.setVisibility(show ? View.VISIBLE : View.GONE);
mSystemStatusViewGroup.setVisibility(areAnyStatusIconsVisible() ? View.VISIBLE : View.GONE);
@@ -174,9 +175,12 @@ public class AmbientStatusBarView extends ConstraintLayout {
void setExtraStatusBarItemViews(List<View> views) {
removeAllExtraStatusBarItemViews();
- views.forEach(view -> mExtraSystemStatusViewGroup.addView(view));
+ views.forEach(view -> {
+ view.setFocusable(true);
+ view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mExtraSystemStatusViewGroup.addView(view);
+ });
}
-
private View fetchStatusIconForResId(int resId) {
final View statusIcon = findViewById(resId);
return Objects.requireNonNull(statusIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index cbdb8827e39c..5cf4b4faed78 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -242,6 +242,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
op.getUid(),
op.getPackageName(),
/* attributionTag= */ attributedOpEntry.getKey(),
+ Context.DEVICE_ID_DEFAULT,
/* active= */ true,
// AppOpsManager doesn't have a way to fetch attribution flags or
// chain ID given an op entry, so default them to none.
@@ -440,14 +441,14 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
* Required to override, delegate to other. Should not be called.
*/
public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
- onOpActiveChanged(op, uid, packageName, null, active,
+ onOpActiveChanged(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT, active,
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
}
// Get active app ops, and check if their attributions are trusted
@Override
public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag,
- boolean active, int attributionFlags, int attributionChainId) {
+ int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId) {
int code = AppOpsManager.strOpToOp(op);
if (DEBUG) {
Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b491c94db151..b6537118324e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,7 +27,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
-import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -64,6 +63,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.plugins.AuthContextPlugins;
import com.android.systemui.biometrics.shared.model.BiometricModalities;
import com.android.systemui.biometrics.shared.model.PromptKind;
import com.android.systemui.biometrics.ui.CredentialView;
@@ -132,6 +132,7 @@ public class AuthContainerView extends LinearLayout
private final int mEffectiveUserId;
private final IBinder mWindowToken = new Binder();
private final ViewCaptureAwareWindowManager mWindowManager;
+ @Nullable private final AuthContextPlugins mAuthContextPlugins;
private final Interpolator mLinearOutSlowIn;
private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -289,6 +290,7 @@ public class AuthContainerView extends LinearLayout
@Nullable List<FaceSensorPropertiesInternal> faceProps,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull UserManager userManager,
+ @Nullable AuthContextPlugins authContextPlugins,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
@@ -306,6 +308,7 @@ public class AuthContainerView extends LinearLayout
WindowManager wm = getContext().getSystemService(WindowManager.class);
mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
enableViewCaptureTracing());
+ mAuthContextPlugins = authContextPlugins;
mWakefulnessLifecycle = wakefulnessLifecycle;
mApplicationCoroutineScope = applicationCoroutineScope;
@@ -316,16 +319,6 @@ public class AuthContainerView extends LinearLayout
mBiometricCallback = new BiometricCallback();
mMSDLPlayer = msdlPlayer;
- // Listener for when device locks from adaptive auth, dismiss prompt
- getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
- getContext().getMainExecutor(),
- isKeyguardLocked -> {
- if (isKeyguardLocked) {
- onStartedGoingToSleep();
- }
- }
- );
-
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
@@ -446,7 +439,7 @@ public class AuthContainerView extends LinearLayout
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel,
- mBiometricCallback);
+ mBiometricCallback, mAuthContextPlugins);
mLayout.addView(mCredentialView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a5bd559dcbf2..316849d90cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -21,11 +21,13 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.systemui.Flags.contAuthPlugin;
import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -74,6 +76,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.plugins.AuthContextPlugins;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
@@ -108,6 +111,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@@ -139,6 +143,7 @@ public class AuthController implements
private final ActivityTaskManager mActivityTaskManager;
@Nullable private final FingerprintManager mFingerprintManager;
@Nullable private final FaceManager mFaceManager;
+ @Nullable private final AuthContextPlugins mContextPlugins;
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final CoroutineScope mApplicationCoroutineScope;
private Job mBiometricContextListenerJob = null;
@@ -717,6 +722,7 @@ public class AuthController implements
@NonNull WindowManager windowManager,
@Nullable FingerprintManager fingerprintManager,
@Nullable FaceManager faceManager,
+ Optional<AuthContextPlugins> contextPlugins,
Provider<UdfpsController> udfpsControllerFactory,
@NonNull DisplayManager displayManager,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@@ -732,6 +738,7 @@ public class AuthController implements
@Background DelayableExecutor bgExecutor,
@NonNull UdfpsUtils udfpsUtils,
@NonNull VibratorHelper vibratorHelper,
+ @NonNull KeyguardManager keyguardManager,
Lazy<ViewCapture> daggerLazyViewCapture,
@NonNull MSDLPlayer msdlPlayer) {
mContext = context;
@@ -744,6 +751,7 @@ public class AuthController implements
mActivityTaskManager = activityTaskManager;
mFingerprintManager = fingerprintManager;
mFaceManager = faceManager;
+ mContextPlugins = contAuthPlugin() ? contextPlugins.orElse(null) : null;
mUdfpsControllerFactory = udfpsControllerFactory;
mUdfpsLogger = udfpsLogger;
mDisplayManager = displayManager;
@@ -762,6 +770,15 @@ public class AuthController implements
mPromptViewModelProvider = promptViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
+ keyguardManager.addKeyguardLockedStateListener(
+ context.getMainExecutor(),
+ isKeyguardLocked -> {
+ if (isKeyguardLocked) {
+ closeDialog("Device lock");
+ }
+ }
+ );
+
mOrientationListener = new BiometricDisplayListener(
context,
mDisplayManager,
@@ -858,6 +875,10 @@ public class AuthController implements
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
mOrientationListener.enable();
updateSensorLocations();
+
+ if (mContextPlugins != null) {
+ mContextPlugins.activate();
+ }
}
@Override
@@ -1350,7 +1371,7 @@ public class AuthController implements
config.mSensorIds = sensorIds;
config.mScaleProvider = this::getScaleFactor;
return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
- wakefulnessLifecycle, userManager, lockPatternUtils,
+ wakefulnessLifecycle, userManager, mContextPlugins, lockPatternUtils,
mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
mLazyViewCapture, mMSDLPlayer);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2863e29c9a34..a9133e45e93f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -814,6 +814,11 @@ public class UdfpsController implements DozeReceiver, Dumpable {
private void showUdfpsOverlay(@NonNull UdfpsControllerOverlay overlay) {
mExecution.assertIsMainThread();
+ if (mOverlay != null) {
+ Log.d(TAG, "showUdfpsOverlay | the overlay is already showing");
+ return;
+ }
+
mOverlay = overlay;
final int requestReason = overlay.getRequestReason();
if (requestReason == REASON_AUTH_KEYGUARD
@@ -823,7 +828,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
return;
}
if (overlay.show(this, mOverlayParams)) {
- Log.v(TAG, "showUdfpsOverlay | adding window reason=" + requestReason);
+ Log.d(TAG, "showUdfpsOverlay | adding window reason=" + requestReason);
mOnFingerDown = false;
mAttemptedToDismissKeyguard = false;
mOrientationListener.enable();
@@ -832,7 +837,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
overlay.getRequestId(), mSensorProps.sensorId);
}
} else {
- Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
+ Log.d(TAG, "showUdfpsOverlay | the overlay is already showing");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 2593cebb14d0..51eb13947d40 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -41,6 +41,7 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
@@ -73,7 +74,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
private const val TAG = "UdfpsControllerOverlay"
@@ -245,7 +245,7 @@ constructor(
return true
}
- Log.v(TAG, "showUdfpsOverlay | the overlay is already showing")
+ Log.d(TAG, "showUdfpsOverlay | the overlay is already showing")
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index e2a8a691b1fd..60ce17721b42 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -36,6 +36,7 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
import com.android.systemui.biometrics.udfps.OverlapDetector
@@ -58,7 +59,7 @@ import javax.inject.Qualifier
/** Dagger module for all things biometric. */
@Module
interface BiometricsModule {
- /** Starts AuthController. */
+ /** Starts AuthController. */
@Binds
@IntoMap
@ClassKey(AuthController::class)
@@ -103,8 +104,9 @@ interface BiometricsModule {
@SysUISingleton
fun displayStateRepository(impl: DisplayStateRepositoryImpl): DisplayStateRepository
- @BindsOptionalOf
- fun deviceEntryUnlockTrackerViewBinder(): DeviceEntryUnlockTrackerViewBinder
+ @BindsOptionalOf fun authContextPlugins(): AuthContextPlugins
+
+ @BindsOptionalOf fun deviceEntryUnlockTrackerViewBinder(): DeviceEntryUnlockTrackerViewBinder
companion object {
/** Background [Executor] for HAL related operations. */
@@ -117,8 +119,7 @@ interface BiometricsModule {
@Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
- @Provides
- fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
+ @Provides fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
@Provides
@SysUISingleton
@@ -136,7 +137,7 @@ interface BiometricsModule {
EllipseOverlapDetectorParams(
minOverlap = values[3],
targetSize = values[2],
- stepSize = values[4].toInt()
+ stepSize = values[4].toInt(),
)
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 976329580c60..8a5e011cd3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -29,6 +29,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -39,7 +40,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlin.math.max
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@SysUISingleton
@@ -55,10 +55,7 @@ constructor(
private fun calculateIconSize(): Int {
val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
if (pixelPitch <= 0) {
- Log.e(
- "UdfpsOverlayInteractor",
- "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
- )
+ Log.e(TAG, "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.")
}
return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
}
@@ -84,13 +81,15 @@ constructor(
}
/** Sets whether Udfps overlay should handle touches */
- fun setHandleTouches(shouldHandle: Boolean = true) {
- if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
+ fun setHandleTouches(shouldHandle: Boolean) {
+ if (authController.isUdfpsSupported) {
fingerprintManager?.setIgnoreDisplayTouches(
requestId.value,
authController.udfpsProps!!.get(0).sensorId,
!shouldHandle,
)
+ } else {
+ Log.d(TAG, "setIgnoreDisplayTouches not set, UDFPS not supported")
}
_shouldHandleTouches.value = shouldHandle
}
@@ -123,12 +122,14 @@ constructor(
// Padding between the fingerprint icon and its bounding box in pixels.
val iconPadding: Flow<Int> =
- udfpsOverlayParams.map { params ->
- val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
- val nativePadding = (sensorWidth - iconSize) / 2
- // padding can be negative when udfpsOverlayParams has not been initialized yet.
- max(0, (nativePadding * params.scaleFactor).toInt())
- }.distinctUntilChanged()
+ udfpsOverlayParams
+ .map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ // padding can be negative when udfpsOverlayParams has not been initialized yet.
+ max(0, (nativePadding * params.scaleFactor).toInt())
+ }
+ .distinctUntilChanged()
companion object {
private const val TAG = "UdfpsOverlayInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.kt b/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.kt
new file mode 100644
index 000000000000..ca38e9869ed1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.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.biometrics.plugins
+
+import com.android.systemui.plugins.AuthContextPlugin
+import com.android.systemui.plugins.PluginManager
+
+/** Wrapper interface for registering & forwarding events to all available [AuthContextPlugin]s. */
+interface AuthContextPlugins {
+ /** Finds and actives all plugins via SysUI's [PluginManager] (should be called at startup). */
+ fun activate()
+
+ /**
+ * Interact with all registered plugins.
+ *
+ * The provided [block] will be repeated for each available plugin.
+ */
+ suspend fun use(block: (AuthContextPlugin) -> Unit)
+
+ /**
+ * Like [use] but when no existing coroutine context is available.
+ *
+ * The [block] will be run on SysUI's general background context and can, optionally, be
+ * confined to [runOnMain] (defaults to a background thread).
+ */
+ fun useInBackground(runOnMain: Boolean = false, block: (AuthContextPlugin) -> Unit)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index b28733f5cc55..dad140f00cee 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -11,6 +11,7 @@ import android.view.accessibility.AccessibilityManager
import android.widget.LinearLayout
import android.widget.TextView
import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
@@ -33,6 +34,7 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
panelViewController: AuthPanelController,
animatePanel: Boolean,
legacyCallback: Spaghetti.Callback,
+ plugins: AuthContextPlugins?,
) {
CredentialViewBinder.bind(
this,
@@ -40,7 +42,8 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
viewModel,
panelViewController,
animatePanel,
- legacyCallback
+ legacyCallback,
+ plugins,
)
}
@@ -78,7 +81,7 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
0,
statusBarInsets.top,
0,
- if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom
+ if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom,
)
return WindowInsets.CONSUMED
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
index d9d286fe7035..e80a79ba1641 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -8,6 +8,7 @@ import android.view.WindowInsets
import android.view.WindowInsets.Type
import android.widget.LinearLayout
import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
@@ -23,6 +24,7 @@ class CredentialPatternView(context: Context, attrs: AttributeSet?) :
panelViewController: AuthPanelController,
animatePanel: Boolean,
legacyCallback: Spaghetti.Callback,
+ plugins: AuthContextPlugins?,
) {
CredentialViewBinder.bind(
this,
@@ -30,7 +32,8 @@ class CredentialPatternView(context: Context, attrs: AttributeSet?) :
viewModel,
panelViewController,
animatePanel,
- legacyCallback
+ legacyCallback,
+ plugins,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
index e2f98958ab55..f3e49175538f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
@@ -1,6 +1,7 @@
package com.android.systemui.biometrics.ui
import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
@@ -29,5 +30,6 @@ sealed interface CredentialView {
panelViewController: AuthPanelController,
animatePanel: Boolean,
legacyCallback: Spaghetti.Callback,
+ plugins: AuthContextPlugins?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 39543e78f784..10b12117a3a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -9,18 +9,21 @@ import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.biometrics.ui.CredentialPasswordView
import com.android.systemui.biometrics.ui.CredentialPatternView
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.AuthContextPlugin
import com.android.systemui.res.R
import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
private const val ANIMATE_CREDENTIAL_INITIAL_DURATION_MS = 150
@@ -42,6 +45,7 @@ object CredentialViewBinder {
panelViewController: AuthPanelController,
animatePanel: Boolean,
legacyCallback: Spaghetti.Callback,
+ plugins: AuthContextPlugins?,
maxErrorDuration: Long = 3_000L,
requestFocusForInput: Boolean = true,
) {
@@ -72,6 +76,10 @@ object CredentialViewBinder {
}
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ if (plugins != null) {
+ launch { plugins.notifyShowingBPCredential(view) }
+ }
+
// show prompt metadata
launch {
viewModel.header.collect { header ->
@@ -136,6 +144,12 @@ object CredentialViewBinder {
host.onCredentialAttemptsRemaining(info.remaining!!, info.message)
}
}
+
+ try {
+ awaitCancellation()
+ } catch (_: Throwable) {
+ plugins?.notifyHidingBPCredential()
+ }
}
}
@@ -172,3 +186,15 @@ private var TextView.textOrHide: String?
text = if (gone) "" else value
}
get() = text?.toString()
+
+private suspend fun AuthContextPlugins.notifyShowingBPCredential(view: View) = use { plugin ->
+ plugin.onShowingSensitiveSurface(
+ AuthContextPlugin.SensitiveSurface.BiometricPrompt(view = view, isCredential = true)
+ )
+}
+
+private fun AuthContextPlugins.notifyHidingBPCredential() = useInBackground { plugin ->
+ plugin.onHidingSensitiveSurface(
+ AuthContextPlugin.SensitiveSurface.BiometricPrompt(isCredential = true)
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
index bba00506df85..a42ae03b2c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -18,6 +18,7 @@ package com.android.systemui.bouncer.data.repository
import android.content.res.Resources
import com.android.internal.R
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -36,7 +37,7 @@ class EmergencyServicesRepository
constructor(
@Application private val applicationScope: CoroutineScope,
@Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
+ @GlobalConfig configurationRepository: ConfigurationRepository,
) {
/**
* Whether to enable emergency services calls while the SIM card is locked. This is disabled in
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 61cd7c7049f4..641400a50c89 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -71,7 +72,7 @@ constructor(
private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
private val falsingCollector: FalsingCollector,
private val dismissCallbackRegistry: DismissCallbackRegistry,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val trustRepository: TrustRepository,
@Application private val applicationScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index 1aaf4fb9f296..ec9ee916b285 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -37,6 +37,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.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.util.icuMessageFormat
import javax.inject.Inject
@@ -62,7 +63,7 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: SimBouncerRepository,
private val telephonyManager: TelephonyManager,
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val euiccManager: EuiccManager?,
// TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
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 12f06bbd4f5e..8a4cc63f65fb 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
@@ -3,6 +3,8 @@ package com.android.systemui.bouncer.ui.binder
import android.view.ViewGroup
import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags.contAuthPlugin
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
@@ -17,6 +19,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition
import com.android.systemui.log.BouncerLogger
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Lazy
+import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,6 +63,7 @@ class BouncerViewBinder
constructor(
private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
+ private val contextPlugins: Optional<AuthContextPlugins>,
) {
fun bind(view: ViewGroup) {
if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
@@ -85,6 +89,7 @@ constructor(
deps.bouncerMessageInteractor,
deps.bouncerLogger,
deps.selectedUserInteractor,
+ if (contAuthPlugin()) contextPlugins.orElse(null) else null,
)
}
}
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 71eda0c19e6f..434a9ce58c3b 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
@@ -22,11 +22,13 @@ import android.view.ViewGroup
import android.window.OnBackAnimationCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityView
import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
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
@@ -35,10 +37,10 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.AuthContextPlugin
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.filter
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Binds the bouncer container to its view model. */
object KeyguardBouncerViewBinder {
@@ -52,6 +54,7 @@ object KeyguardBouncerViewBinder {
bouncerMessageInteractor: BouncerMessageInteractor,
bouncerLogger: BouncerLogger,
selectedUserInteractor: SelectedUserInteractor,
+ plugins: AuthContextPlugins?,
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
val securityContainerController: KeyguardSecurityContainerController =
@@ -94,7 +97,7 @@ object KeyguardBouncerViewBinder {
override fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
- cancelAction: Runnable?
+ cancelAction: Runnable?,
) {
securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
}
@@ -138,7 +141,7 @@ object KeyguardBouncerViewBinder {
it.bindMessageView(
bouncerMessageInteractor,
messageAreaControllerFactory,
- bouncerLogger
+ bouncerLogger,
)
}
} else {
@@ -149,6 +152,13 @@ object KeyguardBouncerViewBinder {
securityContainerController.reset()
securityContainerController.onPause()
}
+ plugins?.apply {
+ if (isShowing) {
+ notifyBouncerShowing(view)
+ } else {
+ notifyBouncerGone()
+ }
+ }
}
}
@@ -209,7 +219,7 @@ object KeyguardBouncerViewBinder {
securityContainerController.showMessage(
it.message,
it.colorStateList,
- /* animated= */ true
+ /* animated= */ true,
)
viewModel.onMessageShown()
}
@@ -233,8 +243,19 @@ object KeyguardBouncerViewBinder {
awaitCancellation()
} finally {
viewModel.setBouncerViewDelegate(null)
+ plugins?.notifyBouncerGone()
}
}
}
}
}
+
+private suspend fun AuthContextPlugins.notifyBouncerShowing(view: View) = use { plugin ->
+ plugin.onShowingSensitiveSurface(
+ AuthContextPlugin.SensitiveSurface.LockscreenBouncer(view = view)
+ )
+}
+
+private fun AuthContextPlugins.notifyBouncerGone() = useInBackground { plugin ->
+ plugin.onHidingSensitiveSurface(AuthContextPlugin.SensitiveSurface.LockscreenBouncer())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 47d913746ed7..5deb751ced27 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -23,6 +23,7 @@ import android.graphics.Bitmap
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.type
import androidx.core.graphics.drawable.toBitmap
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.coroutines.traceCoroutine
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -35,6 +36,7 @@ import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -48,7 +50,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Models UI state for the content of the bouncer scene. */
class BouncerSceneContentViewModel
@@ -67,6 +68,7 @@ constructor(
private val bouncerHapticPlayer: BouncerHapticPlayer,
private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
private val bouncerActionButtonInteractor: BouncerActionButtonInteractor,
+ private val keyguardDismissActionInteractor: KeyguardDismissActionInteractor,
) : ExclusiveActivatable() {
private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -421,6 +423,13 @@ constructor(
}
}
+ /**
+ * Notifies that the bouncer UI has been destroyed (e.g. the composable left the composition).
+ */
+ fun onUiDestroyed() {
+ keyguardDismissActionInteractor.clearDismissAction()
+ }
+
data class DialogViewModel(
val text: String,
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 5bad9fc9c5d7..6f2a2c4ccaaa 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
@@ -33,6 +33,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -105,11 +106,11 @@ private fun BrightnessSlider(
null
}
- val overriddenByAppState =
+ val overriddenByAppState by
if (Flags.showToastWhenAppControlBrightness()) {
- viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle().value
+ viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
} else {
- false
+ remember { mutableStateOf(false) }
}
PlatformSlider(
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index b2d02edf3c45..a31e61f67e47 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -107,6 +107,7 @@ constructor(
activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
activityOptions.rotationAnimationHint =
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ intent.collectExtraIntentKeys()
try {
activityTaskManager.startActivityAsUser(
null,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
index 074b64e0fab0..69f4f6d30f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -74,3 +74,12 @@ constructor(
/** Returns `true` if the tap gesture should be rejected */
fun isFalseTap(@Penalty penalty: Int): Boolean = manager.isFalseTap(penalty)
}
+
+inline fun FalsingInteractor.runIfNotFalseTap(
+ penalty: Int = FalsingManager.LOW_PENALTY,
+ action: () -> Unit,
+) {
+ if (!isFalseTap(penalty)) {
+ action()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 7fe00322ef27..82bce0b5338a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -179,6 +179,14 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
}
/**
+ * Only for testing. Get mSeekBarListener to the seekbar.
+ */
+ @VisibleForTesting
+ public SeekBarChangeListener getSeekBarChangeListener() {
+ return mSeekBarListener;
+ }
+
+ /**
* Only for testing. Get {@link #mSeekbar} in the layout.
*/
@VisibleForTesting
@@ -289,8 +297,10 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
}
- private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ @VisibleForTesting
+ public class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
+ private boolean mSeekByTouch = false;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -308,6 +318,14 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
} else {
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+ if (!mSeekByTouch) {
+ // Accessibility users could change the progress of the seekbar without
+ // touching the seekbar or clicking the buttons. We will consider the
+ // interaction has finished in this case.
+ mOnSeekBarChangeListener.onUserInteractionFinalized(
+ seekBar,
+ OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
+ }
}
}
updateIconViewIfNeeded(progress);
@@ -315,6 +333,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
+ mSeekByTouch = true;
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
}
@@ -322,6 +341,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
+ mSeekByTouch = false;
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
mOnSeekBarChangeListener.onUserInteractionFinalized(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d648b9c6442b..5644e6b3b9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -23,7 +23,6 @@ import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -218,7 +217,8 @@ constructor(
newScene = CommunalScenes.Blank,
loggingReason = "hub timeout",
transitionKey =
- if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+ if (communalSettingsInteractor.isV2FlagEnabled())
+ CommunalTransitionKeys.SimpleFade
else null,
)
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 44dd34a89303..8ddd1ed04f33 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -34,6 +34,7 @@ import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
+import com.android.systemui.communal.ui.compose.sceneTransitions
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.communal.util.CommunalColorsImpl
import com.android.systemui.communal.widgets.CommunalWidgetModule
@@ -113,6 +114,7 @@ interface CommunalModule {
SceneContainerConfig(
sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
initialSceneKey = CommunalScenes.Blank,
+ transitions = sceneTransitions,
navigationDistances =
mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1),
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
index c3d2683ce953..41ea7b6c2202 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
@@ -29,9 +29,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
/** Utilities for communal backup and restore. */
-class CommunalBackupUtils(
- private val context: Context,
-) {
+class CommunalBackupUtils(private val context: Context) {
/**
* Retrieves a communal hub state protobuf that represents the current state of the communal
@@ -50,6 +48,8 @@ class CommunalBackupUtils(
widgetId = widget.widgetId
componentName = widget.componentName
userSerialNumber = widget.userSerialNumber
+ spanY = widget.spanY
+ spanYNew = widget.spanYNew
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index e72088f37fa7..679d0714b1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,9 +26,11 @@ import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toResponsive
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 5)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
@@ -59,7 +61,12 @@ abstract class CommunalDatabase : RoomDatabase() {
context.resources.getString(R.string.config_communalDatabase),
)
.also { builder ->
- builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
+ builder.addMigrations(
+ MIGRATION_1_2,
+ MIGRATION_2_3,
+ MIGRATION_3_4,
+ MIGRATION_4_5,
+ )
builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
@@ -123,5 +130,30 @@ abstract class CommunalDatabase : RoomDatabase() {
)
}
}
+
+ /** This migration adds a new spanY column for responsive grid sizing. */
+ @VisibleForTesting
+ val MIGRATION_4_5 =
+ object : Migration(4, 5) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 4 to 5")
+ db.execSQL(
+ "ALTER TABLE communal_widget_table " +
+ "ADD COLUMN span_y_new INTEGER NOT NULL DEFAULT 1"
+ )
+ db.query("SELECT item_id, span_y FROM communal_widget_table").use { cursor ->
+ while (cursor.moveToNext()) {
+ val id = cursor.getInt(cursor.getColumnIndex("item_id"))
+ val spanYFixed =
+ SpanValue.Fixed(cursor.getInt(cursor.getColumnIndex("span_y")))
+ val spanYResponsive = spanYFixed.toResponsive()
+ db.execSQL(
+ "UPDATE communal_widget_table SET span_y_new = " +
+ "${spanYResponsive.value} WHERE item_id = $id"
+ )
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index f9d2a843c213..6ef4bb8e55eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -45,7 +45,12 @@ data class CommunalWidgetItem(
* The vertical span of the widget. Span_Y default value corresponds to
* CommunalContentSize.HALF.span
*/
- @ColumnInfo(name = "span_y", defaultValue = "3") val spanY: Int,
+ @Deprecated("Use spanYNew instead")
+ @ColumnInfo(name = "span_y", defaultValue = "3")
+ val spanY: Int,
+
+ /** The vertical span of the widget in grid cell units. */
+ @ColumnInfo(name = "span_y_new", defaultValue = "1") val spanYNew: Int,
) {
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 3d40aa75b488..3907a37cd5d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -27,7 +27,9 @@ import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.communal.nano.CommunalHubState
-import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toFixed
+import com.android.systemui.communal.shared.model.toResponsive
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +103,7 @@ constructor(
componentName = name,
rank = index,
userSerialNumber = userSerialNumber,
+ spanY = SpanValue.Fixed(3),
)
}
}
@@ -155,15 +158,16 @@ interface CommunalWidgetDao {
@Query(
"INSERT INTO communal_widget_table" +
- "(widget_id, component_name, item_id, user_serial_number, span_y) " +
- "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY)"
+ "(widget_id, component_name, item_id, user_serial_number, span_y, span_y_new) " +
+ "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY, :spanYNew)"
)
fun insertWidget(
widgetId: Int,
componentName: String,
itemId: Long,
userSerialNumber: Int,
- spanY: Int = 3,
+ spanY: Int,
+ spanYNew: Int,
): Long
@Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
@@ -189,10 +193,12 @@ interface CommunalWidgetDao {
}
@Transaction
- fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+ fun resizeWidget(appWidgetId: Int, spanY: SpanValue, widgetIdToRankMap: Map<Int, Int>) {
val widget = getWidgetByIdNow(appWidgetId)
if (widget != null) {
- updateWidget(widget.copy(spanY = spanY))
+ updateWidget(
+ widget.copy(spanY = spanY.toFixed().value, spanYNew = spanY.toResponsive().value)
+ )
}
updateWidgetOrder(widgetIdToRankMap)
}
@@ -203,7 +209,7 @@ interface CommunalWidgetDao {
provider: ComponentName,
rank: Int? = null,
userSerialNumber: Int,
- spanY: Int = CommunalContentSize.HALF.span,
+ spanY: SpanValue,
): Long {
return addWidget(
widgetId = widgetId,
@@ -220,7 +226,7 @@ interface CommunalWidgetDao {
componentName: String,
rank: Int? = null,
userSerialNumber: Int,
- spanY: Int = 3,
+ spanY: SpanValue,
): Long {
val widgets = getWidgetsNow()
@@ -241,7 +247,8 @@ interface CommunalWidgetDao {
componentName = componentName,
itemId = insertItemRank(newRank),
userSerialNumber = userSerialNumber,
- spanY = spanY,
+ spanY = spanY.toFixed().value,
+ spanYNew = spanY.toResponsive().value,
)
}
@@ -264,7 +271,11 @@ interface CommunalWidgetDao {
clearCommunalItemRankTable()
state.widgets.forEach {
- val spanY = if (it.spanY != 0) it.spanY else CommunalContentSize.HALF.span
+ // Check if there is a new value to restore. If so, restore that new value.
+ val spanYResponsive = if (it.spanYNew != 0) SpanValue.Responsive(it.spanYNew) else null
+ // If no new value, restore any existing old values.
+ val spanY = spanYResponsive ?: SpanValue.Fixed(it.spanY.coerceIn(3, 6))
+
addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, spanY)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 7b54815ab344..26abb48ce7db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -20,9 +20,11 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.content.res.Resources
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.glanceableHubV2
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.model.CommunalEnabledState
import com.android.systemui.communal.data.model.DisabledReason
@@ -33,6 +35,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_U
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.emitOnStart
@@ -53,13 +56,30 @@ interface CommunalSettingsRepository {
fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
/**
- * Returns true if both the communal trunk-stable flag and resource flag are enabled.
+ * Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
- * The trunk-stable flag is controlled by server rollout and is on all devices. The resource
- * flag is enabled via resource overlay only on products we want the hub to be present on.
+ * This should be used for preventing basic glanceable hub functionality from running on devices
+ * that don't need it.
+ *
+ * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android
+ * config boolean. Otherwise, checks the old config_communalServiceEnabled config and
+ * communal_hub flag.
*/
fun getFlagEnabled(): Boolean
+ /**
+ * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag
+ * are enabled.
+ *
+ * This should be used to flag off new glanceable hub or dream behavior that should launch
+ * together with the new hub experience that brings the hub to mobile.
+ *
+ * The trunk-stable flag is controlled by server rollout and is on all devices. The Android
+ * config flag is enabled via resource overlay only on products we want the hub to be present
+ * on.
+ */
+ fun getV2FlagEnabled(): Boolean
+
/** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
@@ -72,6 +92,7 @@ class CommunalSettingsRepositoryImpl
@Inject
constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val resources: Resources,
private val featureFlagsClassic: FeatureFlagsClassic,
private val secureSettings: SecureSettings,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -79,7 +100,18 @@ constructor(
) : CommunalSettingsRepository {
override fun getFlagEnabled(): Boolean {
- return featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ return if (getV2FlagEnabled()) {
+ true
+ } else {
+ // This config (exposed as a classic feature flag) is targeted only to tablet.
+ // TODO(b/379181581): clean up usages of communal_hub flag
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ }
+ }
+
+ override fun getV2FlagEnabled(): Boolean {
+ return resources.getBoolean(com.android.internal.R.bool.config_glanceableHubEnabled) &&
+ glanceableHubV2()
}
override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
@@ -128,7 +160,7 @@ constructor(
secureSettings.getIntForUser(
GLANCEABLE_HUB_BACKGROUND_SETTING,
CommunalBackgroundType.ANIMATED.value,
- user.id
+ user.id,
)
CommunalBackgroundType.entries.find { type -> type.value == intType }
?: CommunalBackgroundType.ANIMATED
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 29569f8b7df5..e44d78baeb35 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.os.UserHandle
import android.os.UserManager
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.Flags.communalWidgetResizing
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
@@ -33,6 +34,7 @@ import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -143,15 +145,21 @@ constructor(
componentName = widget.componentName,
rank = rank.rank,
providerInfo = providers[widget.widgetId],
- spanY = widget.spanY,
+ spanY = if (communalResponsiveGrid()) widget.spanYNew else widget.spanY,
)
}
}
override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
if (!communalWidgetResizing()) return
+ val spanValue =
+ if (communalResponsiveGrid()) {
+ SpanValue.Responsive(spanY)
+ } else {
+ SpanValue.Fixed(spanY)
+ }
bgScope.launch {
- communalWidgetDao.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+ communalWidgetDao.resizeWidget(appWidgetId, spanValue, widgetIdToRankMap)
logger.i({ "Updated spanY of widget $int1 to $int2." }) {
int1 = appWidgetId
int2 = spanY
@@ -225,7 +233,7 @@ constructor(
provider = provider,
rank = rank,
userSerialNumber = userManager.getUserSerialNumber(user.identifier),
- spanY = 3,
+ spanY = SpanValue.Fixed(3),
)
backupManager.dataChanged()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 602fe307a1fe..f9b30c6c2ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -34,9 +34,9 @@ import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
-import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
-import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.HALF
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.THIRD
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 0cdbad40b2d1..862b05bc9b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -63,24 +63,41 @@ constructor(
.logDiffsForTable(
tableLogBuffer = tableLogBuffer,
columnPrefix = "disabledReason",
- initialValue = CommunalEnabledState()
+ initialValue = CommunalEnabledState(),
)
.map { model -> model.enabled }
// Start this eagerly since the value is accessed synchronously in many places.
.stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
/**
- * Returns true if both the communal trunk-stable flag and resource flag are enabled.
+ * Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
- * The trunk-stable flag is controlled by server rollout and is on all devices. The resource
- * flag is enabled via resource overlay only on products we want the hub to be present on.
+ * This should be used for preventing basic glanceable hub functionality from running on devices
+ * that don't need it.
*
* If this is false, then the hub is definitely not available on the device. If this is true,
* refer to [isCommunalEnabled] which takes into account other factors that can change at
* runtime.
+ *
+ * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android
+ * config boolean. Otherwise, checks the old config_communalServiceEnabled config and
+ * communal_hub flag.
*/
fun isCommunalFlagEnabled(): Boolean = repository.getFlagEnabled()
+ /**
+ * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag
+ * are enabled.
+ *
+ * This should be used to flag off new glanceable hub or dream behavior that should launch
+ * together with the new hub experience that brings the hub to mobile.
+ *
+ * The trunk-stable flag is controlled by server rollout and is on all devices. The Android
+ * config flag is enabled via resource overlay only on products we want the hub to be present
+ * on.
+ */
+ fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled()
+
/** The type of background to use for the hub. Used to experiment with different backgrounds */
val communalBackground: Flow<CommunalBackgroundType> =
userInteractor.selectedUserInfo
@@ -120,6 +137,6 @@ constructor(
.stateIn(
scope = bgScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = null
+ initialValue = null,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 30f580e472e8..da613f58dce8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.widget.RemoteViews
+import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.communal.shared.model.CommunalContentSize
import java.util.UUID
@@ -35,7 +36,7 @@ sealed interface CommunalContentModel {
/** The minimum size content can be resized to. */
val minSize: CommunalContentSize
- get() = CommunalContentSize.HALF
+ get() = fixedHalfOrResponsiveSize()
/**
* A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
@@ -44,7 +45,12 @@ sealed interface CommunalContentModel {
sealed interface Ongoing : CommunalContentModel {
override var size: CommunalContentSize
override val minSize
- get() = CommunalContentSize.THIRD
+ get() =
+ if (communalResponsiveGrid()) {
+ CommunalContentSize.Responsive(1)
+ } else {
+ CommunalContentSize.FixedSize.THIRD
+ }
/** Timestamp in milliseconds of when the content was created. */
val createdTimestampMillis: Long
@@ -100,14 +106,16 @@ sealed interface CommunalContentModel {
class WidgetPlaceholder : CommunalContentModel {
override val key: String = KEY.widgetPlaceholder()
// Same as widget size.
- override val size = CommunalContentSize.HALF
+ override val size: CommunalContentSize
+ get() = fixedHalfOrResponsiveSize()
}
/** A CTA tile in the glanceable hub view mode which can be dismissed. */
class CtaTileInViewMode : CommunalContentModel {
override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY
// Same as widget size.
- override val size = CommunalContentSize.HALF
+ override val size: CommunalContentSize
+ get() = fixedHalfOrResponsiveSize()
}
class Tutorial(id: Int, override var size: CommunalContentSize) : CommunalContentModel {
@@ -118,15 +126,15 @@ sealed interface CommunalContentModel {
smartspaceTargetId: String,
val remoteViews: RemoteViews,
override val createdTimestampMillis: Long,
- override var size: CommunalContentSize = CommunalContentSize.HALF,
+ override var size: CommunalContentSize = fixedHalfOrResponsiveSize(),
) : Ongoing {
override val key = KEY.smartspace(smartspaceTargetId)
}
class Umo(
override val createdTimestampMillis: Long,
- override var size: CommunalContentSize = CommunalContentSize.HALF,
- override var minSize: CommunalContentSize = CommunalContentSize.HALF,
+ override var size: CommunalContentSize = fixedHalfOrResponsiveSize(),
+ override var minSize: CommunalContentSize = fixedHalfOrResponsiveSize(),
) : Ongoing {
override val key = KEY.umo()
}
@@ -170,3 +178,10 @@ sealed interface CommunalContentModel {
fun isLiveContent() = this is Smartspace || this is Umo
}
+
+private fun fixedHalfOrResponsiveSize() =
+ if (communalResponsiveGrid()) {
+ CommunalContentSize.Responsive(1)
+ } else {
+ CommunalContentSize.FixedSize.HALF
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index 7602a7afce4e..04717d0c864b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -39,7 +39,10 @@ message CommunalHubState {
// Serial number of the user associated with the widget.
int32 user_serial_number = 4;
- // The vertical span of the widget
+ // The vertical span of the widget, replaced by span_y_new.
int32 span_y = 5;
+
+ // The vertical span of the widget.
+ int32 span_y_new = 6;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index cf80b7d62fd5..df30716ebaf2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -16,27 +16,39 @@
package com.android.systemui.communal.shared.model
+import com.android.systemui.Flags.communalResponsiveGrid
+
/**
* Supported sizes for communal content in the layout grid.
*
- * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents
- * HALF, 2 represents THIRD, and 1 represents SIXTH.
+ * @property span The span of the content in a column.
*/
-enum class CommunalContentSize(val span: Int) {
- /** Content takes the full height of the column. */
- FULL(6),
+sealed interface CommunalContentSize {
+ val span: Int
+
+ @Deprecated("Use Responsive size instead")
+ enum class FixedSize(override val span: Int) : CommunalContentSize {
+ /** Content takes the full height of the column. */
+ FULL(6),
- /** Content takes half of the height of the column. */
- HALF(3),
+ /** Content takes half of the height of the column. */
+ HALF(3),
+
+ /** Content takes a third of the height of the column. */
+ THIRD(2),
+ }
- /** Content takes a third of the height of the column. */
- THIRD(2);
+ @JvmInline value class Responsive(override val span: Int) : CommunalContentSize
companion object {
/** Converts from span to communal content size. */
fun toSize(span: Int): CommunalContentSize {
- return entries.find { it.span == span }
- ?: throw IllegalArgumentException("$span is not a valid span size")
+ return if (communalResponsiveGrid()) {
+ Responsive(span)
+ } else {
+ FixedSize.entries.find { it.span == span }
+ ?: throw IllegalArgumentException("$span is not a valid span size")
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt
new file mode 100644
index 000000000000..15cc6b0ad2c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.shared.model
+
+/** Models possible span values for different grid formats. */
+sealed interface SpanValue {
+ val value: Int
+
+ @Deprecated("Use Responsive sizes instead")
+ @JvmInline
+ value class Fixed(override val value: Int) : SpanValue
+
+ @JvmInline value class Responsive(override val value: Int) : SpanValue
+}
+
+fun SpanValue.toResponsive(): SpanValue.Responsive =
+ when (this) {
+ is SpanValue.Responsive -> this
+ is SpanValue.Fixed -> SpanValue.Responsive((this.value / 3).coerceAtMost(1))
+ }
+
+fun SpanValue.toFixed(): SpanValue.Fixed =
+ when (this) {
+ is SpanValue.Fixed -> this
+ is SpanValue.Responsive -> SpanValue.Fixed((this.value * 3).coerceIn(3, 6))
+ }
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 9cd6465266d4..eb7420f76f59 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
@@ -90,7 +90,7 @@ constructor(
private val keyguardIndicationController: KeyguardIndicationController,
communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
- communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -372,6 +372,9 @@ constructor(
val communalBackground: Flow<CommunalBackgroundType> =
communalSettingsInteractor.communalBackground
+ /** See [CommunalSettingsInteractor.isV2FlagEnabled] */
+ fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
index cc6007b400f7..50d86a24be96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.os.Bundle
import android.util.SizeF
import com.android.app.tracing.coroutines.withContextTraced as withContext
+import com.android.systemui.Flags
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
@@ -30,6 +31,9 @@ import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.qualifiers.UiBackground
import dagger.Lazy
import java.util.concurrent.Executor
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -53,7 +57,11 @@ constructor(
withContext("$TAG#createWidget", uiBgContext) {
val view =
CommunalAppWidgetHostView(context, interactionHandler).apply {
- setExecutor(uiBgExecutor)
+ if (Flags.communalHubUseThreadPoolForWidgets()) {
+ setExecutor(widgetExecutor)
+ } else {
+ setExecutor(uiBgExecutor)
+ }
setAppWidget(model.appWidgetId, model.providerInfo)
}
@@ -90,5 +98,20 @@ constructor(
private companion object {
const val TAG = "WidgetViewFactory"
+
+ val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
+
+ /**
+ * This executor is used for widget inflation. Parameters match what launcher uses. See
+ * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR].
+ */
+ val widgetExecutor =
+ ThreadPoolExecutor(
+ /*corePoolSize*/ poolSize,
+ /*maxPoolSize*/ poolSize,
+ /*keepAlive*/ 1,
+ /*unit*/ TimeUnit.SECONDS,
+ /*workQueue*/ LinkedBlockingQueue(),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 41edb4a1ffaf..740e011b3d20 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.management
+import android.app.Activity
import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
@@ -34,25 +35,25 @@ import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.res.R
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
-/**
- * Activity for rearranging and removing controls for a given structure
- */
-open class ControlsEditingActivity @Inject constructor(
+/** Activity for rearranging and removing controls for a given structure */
+open class ControlsEditingActivity
+@Inject
+constructor(
@Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
-) : ComponentActivity() {
+) : ComponentActivity(), ControlsManagementActivity {
companion object {
private const val DEBUG = false
@@ -64,6 +65,9 @@ open class ControlsEditingActivity @Inject constructor(
private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
}
+ override val activity: Activity
+ get() = this
+
private lateinit var component: ComponentName
private lateinit var structure: CharSequence
private lateinit var model: FavoritesModel
@@ -73,16 +77,17 @@ open class ControlsEditingActivity @Inject constructor(
private var isFromFavoriting: Boolean = false
- private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
- private val startingUser = controller.currentUserId
+ private val userTrackerCallback: UserTracker.Callback =
+ object : UserTracker.Callback {
+ private val startingUser = controller.currentUserId
- override fun onUserChanged(newUser: Int, userContext: Context) {
- if (newUser != startingUser) {
- userTracker.removeCallback(this)
- finish()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
+ finish()
+ }
}
}
- }
private val mOnBackInvokedCallback = OnBackInvokedCallback {
if (DEBUG) {
@@ -98,9 +103,7 @@ open class ControlsEditingActivity @Inject constructor(
component = it
} ?: run(this::finish)
isFromFavoriting = intent.getBooleanExtra(EXTRA_FROM_FAVORITING, false)
- intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
- structure = it
- } ?: run(this::finish)
+ intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let { structure = it } ?: run(this::finish)
bindViews()
@@ -117,7 +120,9 @@ open class ControlsEditingActivity @Inject constructor(
Log.d(TAG, "Registered onBackInvokedCallback")
}
onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mOnBackInvokedCallback,
+ )
}
override fun onStop() {
@@ -142,18 +147,21 @@ open class ControlsEditingActivity @Inject constructor(
override fun run() {
finish()
}
- }
- ).start()
+ },
+ )
+ .start()
}
private fun bindViews() {
setContentView(R.layout.controls_management)
+ applyInsets(R.id.controls_management_root)
+
lifecycle.addObserver(
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.controls_management_root),
window,
- intent
+ intent,
)
)
@@ -163,81 +171,86 @@ open class ControlsEditingActivity @Inject constructor(
}
requireViewById<TextView>(R.id.title).text = structure
setTitle(structure)
- subtitle = requireViewById<TextView>(R.id.subtitle).apply {
- setText(SUBTITLE_ID)
- }
+ subtitle = requireViewById<TextView>(R.id.subtitle).apply { setText(SUBTITLE_ID) }
}
private fun bindButtons() {
- addControls = requireViewById<Button>(R.id.addControls).apply {
- isEnabled = true
- visibility = View.VISIBLE
- setOnClickListener {
- if (saveButton.isEnabled) {
- // The user has made changes
- Toast.makeText(
- applicationContext,
- R.string.controls_favorite_toast_no_changes,
- Toast.LENGTH_SHORT
- ).show()
- }
- if (isFromFavoriting) {
- animateExitAndFinish()
- } else {
- startActivity(Intent(context, ControlsFavoritingActivity::class.java).also {
- it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
- it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
- it.putExtra(
- ControlsFavoritingActivity.EXTRA_APP,
- intent.getCharSequenceExtra(EXTRA_APP),
- )
- it.putExtra(
- ControlsFavoritingActivity.EXTRA_SOURCE,
- ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
+ addControls =
+ requireViewById<Button>(R.id.addControls).apply {
+ isEnabled = true
+ visibility = View.VISIBLE
+ setOnClickListener {
+ if (saveButton.isEnabled) {
+ // The user has made changes
+ Toast.makeText(
+ applicationContext,
+ R.string.controls_favorite_toast_no_changes,
+ Toast.LENGTH_SHORT,
+ )
+ .show()
+ }
+ if (isFromFavoriting) {
+ animateExitAndFinish()
+ } else {
+ startActivity(
+ Intent(context, ControlsFavoritingActivity::class.java).also {
+ it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+ it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+ it.putExtra(
+ ControlsFavoritingActivity.EXTRA_APP,
+ intent.getCharSequenceExtra(EXTRA_APP),
+ )
+ it.putExtra(
+ ControlsFavoritingActivity.EXTRA_SOURCE,
+ ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
+ )
+ },
+ ActivityOptions.makeSceneTransitionAnimation(
+ this@ControlsEditingActivity
+ )
+ .toBundle(),
)
- },
- ActivityOptions.makeSceneTransitionAnimation(
- this@ControlsEditingActivity
- ).toBundle(),
- )
+ }
}
}
- }
- saveButton = requireViewById<Button>(R.id.done).apply {
- isEnabled = isFromFavoriting
- setText(R.string.save)
- setOnClickListener {
- saveFavorites()
- startActivity(
- Intent(applicationContext, ControlsActivity::class.java),
- ActivityOptions
- .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()
- )
- animateExitAndFinish()
+ saveButton =
+ requireViewById<Button>(R.id.done).apply {
+ isEnabled = isFromFavoriting
+ setText(R.string.save)
+ setOnClickListener {
+ saveFavorites()
+ startActivity(
+ Intent(applicationContext, ControlsActivity::class.java),
+ ActivityOptions.makeSceneTransitionAnimation(this@ControlsEditingActivity)
+ .toBundle(),
+ )
+ animateExitAndFinish()
+ }
}
- }
}
private fun saveFavorites() {
controller.replaceFavoritesForStructure(
- StructureInfo(component, structure, model.favorites))
+ StructureInfo(component, structure, model.favorites)
+ )
}
- private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback {
- override fun onNoneChanged(showNoFavorites: Boolean) {
- if (showNoFavorites) {
- subtitle.setText(EMPTY_TEXT_ID)
- } else {
- subtitle.setText(SUBTITLE_ID)
+ private val favoritesModelCallback =
+ object : FavoritesModel.FavoritesModelCallback {
+ override fun onNoneChanged(showNoFavorites: Boolean) {
+ if (showNoFavorites) {
+ subtitle.setText(EMPTY_TEXT_ID)
+ } else {
+ subtitle.setText(SUBTITLE_ID)
+ }
}
- }
- override fun onChange() = Unit
+ override fun onChange() = Unit
- override fun onFirstChange() {
- saveButton.isEnabled = true
+ override fun onFirstChange() {
+ saveButton.isEnabled = true
+ }
}
- }
private fun setUpList() {
val controls = controller.getFavoritesForStructure(component, structure)
@@ -245,44 +258,55 @@ open class ControlsEditingActivity @Inject constructor(
val elevation = resources.getFloat(R.dimen.control_card_elevation)
val recyclerView = requireViewById<RecyclerView>(R.id.list)
recyclerView.alpha = 0.0f
- val adapter = ControlAdapter(elevation, userTracker.userId).apply {
- registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
- var hasAnimated = false
- override fun onChanged() {
- if (!hasAnimated) {
- hasAnimated = true
- ControlsAnimations.enterAnimation(recyclerView).start()
+ val adapter =
+ ControlAdapter(elevation, userTracker.userId).apply {
+ registerAdapterDataObserver(
+ object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
}
- }
- })
- }
+ )
+ }
- val margin = resources
- .getDimensionPixelSize(R.dimen.controls_card_margin)
+ val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
val itemDecorator = MarginItemDecorator(margin, margin)
val spanCount = ControlAdapter.findMaxColumns(resources)
recyclerView.apply {
this.adapter = adapter
- layoutManager = object : GridLayoutManager(recyclerView.context, spanCount) {
-
- // This will remove from the announcement the row corresponding to the divider,
- // as it's not something that should be announced.
- override fun getRowCountForAccessibility(
- recycler: RecyclerView.Recycler,
- state: RecyclerView.State
- ): Int {
- val initial = super.getRowCountForAccessibility(recycler, state)
- return if (initial > 0) initial - 1 else initial
- }
- }.apply {
- spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
- override fun getSpanSize(position: Int): Int {
- return if (adapter?.getItemViewType(position)
- != ControlAdapter.TYPE_CONTROL) spanCount else 1
+ layoutManager =
+ object : GridLayoutManager(recyclerView.context, spanCount) {
+
+ // This will remove from the announcement the row corresponding to the
+ // divider,
+ // as it's not something that should be announced.
+ override fun getRowCountForAccessibility(
+ recycler: RecyclerView.Recycler,
+ state: RecyclerView.State,
+ ): Int {
+ val initial = super.getRowCountForAccessibility(recycler, state)
+ return if (initial > 0) initial - 1 else initial
+ }
+ }
+ .apply {
+ spanSizeLookup =
+ object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (
+ adapter?.getItemViewType(position) !=
+ ControlAdapter.TYPE_CONTROL
+ )
+ spanCount
+ else 1
+ }
+ }
}
- }
- }
addItemDecoration(itemDecorator)
}
adapter.changeModel(model)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 2ea4303e2290..ab55c5326b55 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -18,6 +18,7 @@ package com.android.systemui.controls.management
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.app.Activity
import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
@@ -39,22 +40,24 @@ import androidx.activity.ComponentActivity
import androidx.annotation.VisibleForTesting
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
-import com.android.systemui.res.R
import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
import javax.inject.Inject
-open class ControlsFavoritingActivity @Inject constructor(
+open class ControlsFavoritingActivity
+@Inject
+constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val userTracker: UserTracker,
-) : ComponentActivity() {
+) : ComponentActivity(), ControlsManagementActivity {
companion object {
private const val DEBUG = false
@@ -74,6 +77,9 @@ open class ControlsFavoritingActivity @Inject constructor(
private const val TOOLTIP_MAX_SHOWN = 2
}
+ override val activity: Activity
+ get() = this
+
private var component: ComponentName? = null
private var appName: CharSequence? = null
private var structureExtra: CharSequence? = null
@@ -95,18 +101,21 @@ open class ControlsFavoritingActivity @Inject constructor(
private val fromProviderSelector: Boolean
get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
+
private val fromEditing: Boolean
get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
- private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
- private val startingUser = controller.currentUserId
- override fun onUserChanged(newUser: Int, userContext: Context) {
- if (newUser != startingUser) {
- userTracker.removeCallback(this)
- finish()
+ private val userTrackerCallback: UserTracker.Callback =
+ object : UserTracker.Callback {
+ private val startingUser = controller.currentUserId
+
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
+ finish()
+ }
}
}
- }
private val mOnBackInvokedCallback = OnBackInvokedCallback {
if (DEBUG) {
@@ -138,81 +147,113 @@ open class ControlsFavoritingActivity @Inject constructor(
bindViews()
}
- private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
- override fun onFirstChange() {
- doneButton.isEnabled = true
- }
+ private val controlsModelCallback =
+ object : ControlsModel.ControlsModelCallback {
+ override fun onFirstChange() {
+ doneButton.isEnabled = true
+ }
- override fun onChange() {
- val structure: StructureContainer = listOfStructures[structurePager.currentItem]
- rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+ override fun onChange() {
+ val structure: StructureContainer = listOfStructures[structurePager.currentItem]
+ rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+ }
}
- }
private fun loadControls() {
component?.let { componentName ->
statusText.text = resources.getText(com.android.internal.R.string.loading)
- val emptyZoneString = resources.getText(
- R.string.controls_favorite_other_zone_header)
- controller.loadForComponent(componentName, { data ->
- val allControls = data.allControls
- val favoriteKeys = data.favoritesIds
- val error = data.errorOnLoad
- val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
- listOfStructures = controlsByStructure.map {
- StructureContainer(it.key, AllModel(
- it.value, favoriteKeys, emptyZoneString, controlsModelCallback))
- }.sortedWith(comparator)
-
- val structureIndex = listOfStructures.indexOfFirst {
- sc -> sc.structureName == structureExtra
- }.let { if (it == -1) 0 else it }
-
- // If we were requested to show a single structure, set the list to just that one
- if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) {
- listOfStructures = listOf(listOfStructures[structureIndex])
- }
+ val emptyZoneString = resources.getText(R.string.controls_favorite_other_zone_header)
+ controller.loadForComponent(
+ componentName,
+ { data ->
+ val allControls = data.allControls
+ val favoriteKeys = data.favoritesIds
+ val error = data.errorOnLoad
+ val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
+ listOfStructures =
+ controlsByStructure
+ .map {
+ StructureContainer(
+ it.key,
+ AllModel(
+ it.value,
+ favoriteKeys,
+ emptyZoneString,
+ controlsModelCallback,
+ ),
+ )
+ }
+ .sortedWith(comparator)
+
+ val structureIndex =
+ listOfStructures
+ .indexOfFirst { sc -> sc.structureName == structureExtra }
+ .let { if (it == -1) 0 else it }
+
+ // If we were requested to show a single structure, set the list to just that
+ // one
+ if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) {
+ listOfStructures = listOf(listOfStructures[structureIndex])
+ }
- executor.execute {
- structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId)
- structurePager.setCurrentItem(structureIndex)
- if (error) {
- statusText.text = resources.getString(R.string.controls_favorite_load_error,
- appName ?: "")
- subtitleView.visibility = View.GONE
- } else if (listOfStructures.isEmpty()) {
- statusText.text = resources.getString(R.string.controls_favorite_load_none)
- subtitleView.visibility = View.GONE
- } else {
- statusText.visibility = View.GONE
-
- pageIndicator.setNumPages(listOfStructures.size)
- pageIndicator.setLocation(0f)
- pageIndicator.visibility =
- if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE
-
- ControlsAnimations.enterAnimation(pageIndicator).apply {
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // Position the tooltip if necessary after animations are complete
- // so we can get the position on screen. The tooltip is not
- // rooted in the layout root.
- if (pageIndicator.visibility == View.VISIBLE &&
- mTooltipManager != null) {
- val p = IntArray(2)
- pageIndicator.getLocationOnScreen(p)
- val x = p[0] + pageIndicator.width / 2
- val y = p[1] + pageIndicator.height
- mTooltipManager?.show(
- R.string.controls_structure_tooltip, x, y)
- }
+ executor.execute {
+ structurePager.adapter =
+ StructureAdapter(listOfStructures, userTracker.userId)
+ structurePager.setCurrentItem(structureIndex)
+ if (error) {
+ statusText.text =
+ resources.getString(
+ R.string.controls_favorite_load_error,
+ appName ?: "",
+ )
+ subtitleView.visibility = View.GONE
+ } else if (listOfStructures.isEmpty()) {
+ statusText.text =
+ resources.getString(R.string.controls_favorite_load_none)
+ subtitleView.visibility = View.GONE
+ } else {
+ statusText.visibility = View.GONE
+
+ pageIndicator.setNumPages(listOfStructures.size)
+ pageIndicator.setLocation(0f)
+ pageIndicator.visibility =
+ if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE
+
+ ControlsAnimations.enterAnimation(pageIndicator)
+ .apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // Position the tooltip if necessary after
+ // animations are complete
+ // so we can get the position on screen. The tooltip
+ // is not
+ // rooted in the layout root.
+ if (
+ pageIndicator.visibility == View.VISIBLE &&
+ mTooltipManager != null
+ ) {
+ val p = IntArray(2)
+ pageIndicator.getLocationOnScreen(p)
+ val x = p[0] + pageIndicator.width / 2
+ val y = p[1] + pageIndicator.height
+ mTooltipManager?.show(
+ R.string.controls_structure_tooltip,
+ x,
+ y,
+ )
+ }
+ }
+ }
+ )
}
- })
- }.start()
- ControlsAnimations.enterAnimation(structurePager).start()
+ .start()
+ ControlsAnimations.enterAnimation(structurePager).start()
+ }
}
- }
- }, { runnable -> cancelLoadRunnable = runnable })
+ },
+ { runnable -> cancelLoadRunnable = runnable },
+ )
}
}
@@ -221,35 +262,39 @@ open class ControlsFavoritingActivity @Inject constructor(
pageIndicator.alpha = 0.0f
structurePager.apply {
adapter = StructureAdapter(emptyList(), userTracker.userId)
- registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- super.onPageSelected(position)
- val name = listOfStructures[position].structureName
- val title = if (!TextUtils.isEmpty(name)) name else appName
- titleView.text = title
- titleView.requestFocus()
- }
+ registerOnPageChangeCallback(
+ object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ val name = listOfStructures[position].structureName
+ val title = if (!TextUtils.isEmpty(name)) name else appName
+ titleView.text = title
+ titleView.requestFocus()
+ }
- override fun onPageScrolled(
- position: Int,
- positionOffset: Float,
- positionOffsetPixels: Int
- ) {
- super.onPageScrolled(position, positionOffset, positionOffsetPixels)
- pageIndicator.setLocation(position + positionOffset)
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int,
+ ) {
+ super.onPageScrolled(position, positionOffset, positionOffsetPixels)
+ pageIndicator.setLocation(position + positionOffset)
+ }
}
- })
+ )
}
}
private fun bindViews() {
setContentView(R.layout.controls_management)
+ applyInsets(R.id.controls_management_root)
+
lifecycle.addObserver(
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.controls_management_root),
window,
- intent
+ intent,
)
)
@@ -260,41 +305,43 @@ open class ControlsFavoritingActivity @Inject constructor(
statusText = requireViewById(R.id.status_message)
if (shouldShowTooltip()) {
- mTooltipManager = TooltipManager(statusText.context,
- TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN)
+ mTooltipManager =
+ TooltipManager(statusText.context, TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN)
addContentView(
mTooltipManager?.layout,
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
- Gravity.TOP or Gravity.LEFT
- )
+ Gravity.TOP or Gravity.LEFT,
+ ),
)
}
- pageIndicator = requireViewById<ManagementPageIndicator>(
- R.id.structure_page_indicator).apply {
- visibilityListener = {
- if (it != View.VISIBLE) {
- mTooltipManager?.hide(true)
+ pageIndicator =
+ requireViewById<ManagementPageIndicator>(R.id.structure_page_indicator).apply {
+ visibilityListener = {
+ if (it != View.VISIBLE) {
+ mTooltipManager?.hide(true)
+ }
}
}
- }
- val title = structureExtra
- ?: (appName ?: resources.getText(R.string.controls_favorite_default_title))
- titleView = requireViewById<TextView>(R.id.title).apply {
- text = title
- }
- subtitleView = requireViewById<TextView>(R.id.subtitle).apply {
- text = resources.getText(R.string.controls_favorite_subtitle)
- }
+ val title =
+ structureExtra
+ ?: (appName ?: resources.getText(R.string.controls_favorite_default_title))
+ titleView = requireViewById<TextView>(R.id.title).apply { text = title }
+ subtitleView =
+ requireViewById<TextView>(R.id.subtitle).apply {
+ text = resources.getText(R.string.controls_favorite_subtitle)
+ }
structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
- structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- super.onPageSelected(position)
- mTooltipManager?.hide(true)
+ structurePager.registerOnPageChangeCallback(
+ object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+ mTooltipManager?.hide(true)
+ }
}
- })
+ )
bindButtons()
}
@@ -307,47 +354,53 @@ open class ControlsFavoritingActivity @Inject constructor(
override fun run() {
finish()
}
- }
- ).start()
+ },
+ )
+ .start()
}
private fun bindButtons() {
- rearrangeButton = requireViewById<Button>(R.id.rearrange).apply {
- text = if (fromEditing) {
- getString(R.string.controls_favorite_back_to_editing)
- } else {
- getString(R.string.controls_favorite_rearrange_button)
- }
- isEnabled = false
- visibility = View.VISIBLE
- setOnClickListener {
- if (component == null) return@setOnClickListener
- saveFavorites()
- startActivity(
- Intent(context, ControlsEditingActivity::class.java).also {
- it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
- it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
- it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
- it.putExtra(
- ControlsEditingActivity.EXTRA_STRUCTURE,
- listOfStructures[structurePager.currentItem].structureName,
- )
- },
- ActivityOptions
- .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
- )
+ rearrangeButton =
+ requireViewById<Button>(R.id.rearrange).apply {
+ text =
+ if (fromEditing) {
+ getString(R.string.controls_favorite_back_to_editing)
+ } else {
+ getString(R.string.controls_favorite_rearrange_button)
+ }
+ isEnabled = false
+ visibility = View.VISIBLE
+ setOnClickListener {
+ if (component == null) return@setOnClickListener
+ saveFavorites()
+ startActivity(
+ Intent(context, ControlsEditingActivity::class.java).also {
+ it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+ it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
+ it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
+ it.putExtra(
+ ControlsEditingActivity.EXTRA_STRUCTURE,
+ listOfStructures[structurePager.currentItem].structureName,
+ )
+ },
+ ActivityOptions.makeSceneTransitionAnimation(
+ this@ControlsFavoritingActivity
+ )
+ .toBundle(),
+ )
+ }
}
- }
- doneButton = requireViewById<Button>(R.id.done).apply {
- isEnabled = false
- setOnClickListener {
- if (component == null) return@setOnClickListener
- saveFavorites()
- animateExitAndFinish()
- openControlsOrigin()
+ doneButton =
+ requireViewById<Button>(R.id.done).apply {
+ isEnabled = false
+ setOnClickListener {
+ if (component == null) return@setOnClickListener
+ saveFavorites()
+ animateExitAndFinish()
+ openControlsOrigin()
+ }
}
- }
}
private fun saveFavorites() {
@@ -362,14 +415,14 @@ open class ControlsFavoritingActivity @Inject constructor(
private fun openControlsOrigin() {
startActivity(
Intent(applicationContext, ControlsActivity::class.java),
- ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+ ActivityOptions.makeSceneTransitionAnimation(this).toBundle(),
)
}
override fun onPause() {
super.onPause()
mTooltipManager?.hide(false)
- }
+ }
override fun onStart() {
super.onStart()
@@ -380,7 +433,9 @@ open class ControlsFavoritingActivity @Inject constructor(
Log.d(TAG, "Registered onBackInvokedCallback")
}
onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mOnBackInvokedCallback,
+ )
}
override fun onResume() {
@@ -393,7 +448,7 @@ open class ControlsFavoritingActivity @Inject constructor(
loadControls()
isPagerLoaded = true
}
- }
+ }
override fun onStop() {
super.onStop()
@@ -403,8 +458,7 @@ open class ControlsFavoritingActivity @Inject constructor(
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
}
- onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
- mOnBackInvokedCallback)
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
}
override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt
new file mode 100644
index 000000000000..c3b8cff7e4eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.controls.management
+
+import android.app.Activity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
+
+interface ControlsManagementActivity {
+ val activity: Activity
+}
+
+fun ControlsManagementActivity.applyInsets(viewId: Int) {
+ activity.requireViewById<ViewGroup>(viewId).apply {
+ setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+ v.apply {
+ val paddings = insets.getInsets(Type.systemBars() or Type.displayCutout())
+ setPadding(paddings.left, paddings.top, paddings.right, paddings.bottom)
+ }
+
+ WindowInsets.CONSUMED
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 10a0117f8757..f56cd00baa15 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.management
+import android.app.Activity
import android.app.ActivityOptions
import android.app.Dialog
import android.content.ComponentName
@@ -35,7 +36,6 @@ import androidx.activity.ComponentActivity
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.res.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -43,40 +43,46 @@ import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
-/**
- * Activity to select an application to favorite the [Control] provided by them.
- */
-open class ControlsProviderSelectorActivity @Inject constructor(
+/** Activity to select an application to favorite the [Control] provided by them. */
+open class ControlsProviderSelectorActivity
+@Inject
+constructor(
@Main private val executor: Executor,
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
private val userTracker: UserTracker,
private val authorizedPanelsRepository: AuthorizedPanelsRepository,
- private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory
-) : ComponentActivity() {
+ private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory,
+) : ComponentActivity(), ControlsManagementActivity {
companion object {
private const val DEBUG = false
private const val TAG = "ControlsProviderSelectorActivity"
const val BACK_SHOULD_EXIT = "back_should_exit"
}
+
+ override val activity: Activity
+ get() = this
+
private var backShouldExit = false
private lateinit var recyclerView: RecyclerView
- private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
- private val startingUser = listingController.currentUserId
+ private val userTrackerCallback: UserTracker.Callback =
+ object : UserTracker.Callback {
+ private val startingUser = listingController.currentUserId
- override fun onUserChanged(newUser: Int, userContext: Context) {
- if (newUser != startingUser) {
- userTracker.removeCallback(this)
- finish()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
+ finish()
+ }
}
}
- }
private var dialog: Dialog? = null
private val mOnBackInvokedCallback = OnBackInvokedCallback {
@@ -95,10 +101,12 @@ open class ControlsProviderSelectorActivity @Inject constructor(
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.controls_management_root),
window,
- intent
+ intent,
)
)
+ applyInsets(R.id.controls_management_root)
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_apps
inflate()
@@ -114,9 +122,7 @@ open class ControlsProviderSelectorActivity @Inject constructor(
requireViewById<Button>(R.id.other_apps).apply {
visibility = View.VISIBLE
setText(com.android.internal.R.string.cancel)
- setOnClickListener {
- onBackPressed()
- }
+ setOnClickListener { onBackPressed() }
}
requireViewById<View>(R.id.done).visibility = View.GONE
@@ -125,9 +131,10 @@ open class ControlsProviderSelectorActivity @Inject constructor(
override fun onBackPressed() {
if (!backShouldExit) {
- val i = Intent().apply {
- component = ComponentName(applicationContext, ControlsActivity::class.java)
- }
+ val i =
+ Intent().apply {
+ component = ComponentName(applicationContext, ControlsActivity::class.java)
+ }
startActivity(i, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
animateExitAndFinish()
@@ -138,33 +145,40 @@ open class ControlsProviderSelectorActivity @Inject constructor(
userTracker.addCallback(userTrackerCallback, executor)
recyclerView.alpha = 0.0f
- recyclerView.adapter = AppAdapter(
- backExecutor,
- executor,
- lifecycle,
- listingController,
- LayoutInflater.from(this),
- ::onAppSelected,
- FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
- resources,
- authorizedPanelsRepository.getAuthorizedPanels()
- ).apply {
- registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
- var hasAnimated = false
- override fun onChanged() {
- if (!hasAnimated) {
- hasAnimated = true
- ControlsAnimations.enterAnimation(recyclerView).start()
- }
+ recyclerView.adapter =
+ AppAdapter(
+ backExecutor,
+ executor,
+ lifecycle,
+ listingController,
+ LayoutInflater.from(this),
+ ::onAppSelected,
+ FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
+ resources,
+ authorizedPanelsRepository.getAuthorizedPanels(),
+ )
+ .apply {
+ registerAdapterDataObserver(
+ object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
+ }
+ )
}
- })
- }
if (DEBUG) {
Log.d(TAG, "Registered onBackInvokedCallback")
}
onBackInvokedDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mOnBackInvokedCallback,
+ )
}
override fun onStop() {
@@ -184,38 +198,45 @@ open class ControlsProviderSelectorActivity @Inject constructor(
launchFavoritingActivity(serviceInfo.componentName)
} else {
val appName = serviceInfo.loadLabel() ?: ""
- dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok ->
- if (ok) {
- authorizedPanelsRepository.addAuthorizedPanels(
- setOf(serviceInfo.componentName.packageName)
- )
- val selected = SelectedItem.PanelItem(appName, serviceInfo.componentName)
- controlsController.setPreferredSelection(selected)
- animateExitAndFinish()
- openControlsOrigin()
- }
- dialog = null
- }.also { it.show() }
+ dialog =
+ panelConfirmationDialogFactory
+ .createConfirmationDialog(this, appName) { ok ->
+ if (ok) {
+ authorizedPanelsRepository.addAuthorizedPanels(
+ setOf(serviceInfo.componentName.packageName)
+ )
+ val selected =
+ SelectedItem.PanelItem(appName, serviceInfo.componentName)
+ controlsController.setPreferredSelection(selected)
+ animateExitAndFinish()
+ openControlsOrigin()
+ }
+ dialog = null
+ }
+ .also { it.show() }
}
}
/**
* Launch the [ControlsFavoritingActivity] for the specified component.
+ *
* @param component a component name for a [ControlsProviderService]
*/
private fun launchFavoritingActivity(component: ComponentName?) {
executor.execute {
component?.let {
- val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
- .apply {
- putExtra(ControlsFavoritingActivity.EXTRA_APP,
- listingController.getAppLabel(it))
- putExtra(Intent.EXTRA_COMPONENT_NAME, it)
- putExtra(
- ControlsFavoritingActivity.EXTRA_SOURCE,
- ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
- )
- }
+ val intent =
+ Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+ putExtra(
+ ControlsFavoritingActivity.EXTRA_APP,
+ listingController.getAppLabel(it),
+ )
+ putExtra(Intent.EXTRA_COMPONENT_NAME, it)
+ putExtra(
+ ControlsFavoritingActivity.EXTRA_SOURCE,
+ ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+ )
+ }
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
}
@@ -228,8 +249,8 @@ open class ControlsProviderSelectorActivity @Inject constructor(
private fun openControlsOrigin() {
startActivity(
- Intent(applicationContext, ControlsActivity::class.java),
- ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+ Intent(applicationContext, ControlsActivity::class.java),
+ ActivityOptions.makeSceneTransitionAnimation(this).toBundle(),
)
}
@@ -242,7 +263,8 @@ open class ControlsProviderSelectorActivity @Inject constructor(
override fun run() {
finish()
}
- }
- ).start()
+ },
+ )
+ .start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 955a5a35d82f..8296ec2311b6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.ui
+import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -25,36 +26,40 @@ import android.content.res.Configuration
import android.os.Bundle
import android.os.RemoteException
import android.service.dreams.IDreamManager
-import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowInsets.Type
import android.view.WindowManager
import androidx.activity.ComponentActivity
-import com.android.systemui.res.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.controls.management.ControlsManagementActivity
+import com.android.systemui.controls.management.applyInsets
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
/**
* Displays Device Controls inside an activity. This activity is available to be displayed over the
* lockscreen if the user has allowed it via
- * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
- * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
- * user expectations for the activity to not continue running.
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be destroyed on
+ * SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as user
+ * expectations for the activity to not continue running.
*/
// Open for testing
-open class ControlsActivity @Inject constructor(
+open class ControlsActivity
+@Inject
+constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
private val featureFlags: FeatureFlags,
private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
- private val keyguardStateController: KeyguardStateController
-) : ComponentActivity() {
+ private val keyguardStateController: KeyguardStateController,
+) : ComponentActivity(), ControlsManagementActivity {
+
+ override val activity: Activity
+ get() = this
private val lastConfiguration = Configuration()
@@ -69,38 +74,27 @@ open class ControlsActivity @Inject constructor(
setContentView(R.layout.controls_fullscreen)
+ applyInsets(R.id.control_detail_root)
+
lifecycle.addObserver(
ControlsAnimations.observerForAnimations(
requireViewById(R.id.control_detail_root),
window,
intent,
- false
+ false,
)
)
- requireViewById<ViewGroup>(R.id.control_detail_root).apply {
- setOnApplyWindowInsetsListener {
- v: View, insets: WindowInsets ->
- v.apply {
- val l = getPaddingLeft()
- val t = getPaddingTop()
- val r = getPaddingRight()
- setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
- }
-
- WindowInsets.CONSUMED
- }
- }
-
initBroadcastReceiver()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- val interestingFlags = ActivityInfo.CONFIG_ORIENTATION or
+ val interestingFlags =
+ ActivityInfo.CONFIG_ORIENTATION or
ActivityInfo.CONFIG_SCREEN_SIZE or
ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
- if (lastConfiguration.diff(newConfig) and interestingFlags != 0 ) {
+ if (lastConfiguration.diff(newConfig) and interestingFlags != 0) {
uiController.onSizeChange()
}
lastConfiguration.setTo(newConfig)
@@ -164,15 +158,18 @@ open class ControlsActivity @Inject constructor(
}
private fun initBroadcastReceiver() {
- broadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.getAction()
- if (action == Intent.ACTION_SCREEN_OFF ||
- action == Intent.ACTION_DREAMING_STARTED) {
- finish()
+ broadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (
+ action == Intent.ACTION_SCREEN_OFF ||
+ action == Intent.ACTION_DREAMING_STARTED
+ ) {
+ finish()
+ }
}
}
- }
val filter = IntentFilter()
filter.addAction(Intent.ACTION_SCREEN_OFF)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index e78ce3bbb0d1..f804c2e80ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -24,6 +24,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.util.InitializationChecker;
+import com.android.wm.shell.dagger.WMComponent;
import dagger.BindsInstance;
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index e862525623fe..9b181be93b61 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -30,6 +30,7 @@ import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractorImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -41,7 +42,7 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
/** Module binding display related classes. */
-@Module
+@Module(includes = [DisplayWindowPropertiesInteractorModule::class])
interface DisplayModule {
@Binds
fun bindConnectedDisplayInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 1fa829a675ec..e5acb8235a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -66,10 +66,22 @@ interface DisplayRepository {
/** Display removal event indicating a display has been removed. */
val displayRemovalEvent: Flow<Int>
- /** Provides the current set of displays. */
+ /**
+ * Provides the current set of displays.
+ *
+ * Consider using [displayIds] if only the [Display.getDisplayId] is needed.
+ */
val displays: StateFlow<Set<Display>>
/**
+ * Provides the current set of display ids.
+ *
+ * Note that it is preferred to use this instead of [displays] if only the
+ * [Display.getDisplayId] is needed.
+ */
+ val displayIds: StateFlow<Set<Int>>
+
+ /**
* Pending display id that can be enabled/disabled.
*
* When `null`, it means there is no pending display waiting to be enabled.
@@ -159,7 +171,7 @@ constructor(
private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet()
/** Propagate to the listeners only enabled displays */
- private val enabledDisplayIds: Flow<Set<Int>> =
+ private val enabledDisplayIds: StateFlow<Set<Int>> =
allDisplayEvents
.scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent ->
val id = event.displayId
@@ -170,8 +182,8 @@ constructor(
}
}
.distinctUntilChanged()
- .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
.debugLog("enabledDisplayIds")
+ .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
private val defaultDisplay by lazy {
getDisplayFromDisplayManager(Display.DEFAULT_DISPLAY)
@@ -209,6 +221,8 @@ constructor(
*/
override val displays: StateFlow<Set<Display>> = enabledDisplays
+ override val displayIds: StateFlow<Set<Int>> = enabledDisplayIds
+
/**
* Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons:
* 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt
new file mode 100644
index 000000000000..22e467bd5e3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.display.domain.interactor
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+/** Provides per display instances of [DisplayWindowProperties]. */
+interface DisplayWindowPropertiesInteractor {
+
+ /**
+ * Returns a [DisplayWindowProperties] instance for a given display id, to be used for the
+ * status bar.
+ *
+ * @throws IllegalArgumentException if no display with the given display id exists.
+ */
+ fun getForStatusBar(displayId: Int): DisplayWindowProperties
+}
+
+@SysUISingleton
+class DisplayWindowPropertiesInteractorImpl
+@Inject
+constructor(private val repo: DisplayWindowPropertiesRepository) :
+ DisplayWindowPropertiesInteractor {
+
+ override fun getForStatusBar(displayId: Int): DisplayWindowProperties {
+ return repo.get(displayId, TYPE_STATUS_BAR)
+ }
+}
+
+@Module
+interface DisplayWindowPropertiesInteractorModule {
+
+ @Binds
+ fun interactor(impl: DisplayWindowPropertiesInteractorImpl): DisplayWindowPropertiesInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
index 12ceedd9cf5a..b990e4cede81 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
@@ -31,15 +31,17 @@ class DozeTransitionListener @Inject constructor() :
override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
this.oldState = oldState
this.newState = newState
- callbacks.forEach { it.onDozeTransition(oldState, newState) }
+
+ val cbs = synchronized(this) { callbacks.toSet() }
+ cbs.forEach { it.onDozeTransition(oldState, newState) }
}
override fun addCallback(callback: DozeTransitionCallback) {
- callbacks.add(callback)
+ synchronized(this) { callbacks.add(callback) }
}
override fun removeCallback(callback: DozeTransitionCallback) {
- callbacks.remove(callback)
+ synchronized(this) { callbacks.remove(callback) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
index e76fd47c74de..c425bee74b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
@@ -25,7 +25,7 @@ import android.os.PatternMatcher
import android.os.RemoteException
import android.service.dreams.IDreamManager
import android.util.Log
-import com.android.systemui.Flags
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.qualifiers.SystemUser
import com.android.systemui.dreams.dagger.DreamModule
import com.android.systemui.log.LogBuffer
@@ -48,6 +48,7 @@ constructor(
@SystemUser monitor: Monitor,
private val packageManager: PackageManager,
private val dreamManager: IDreamManager,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
@DreamLog private val logBuffer: LogBuffer,
) : ConditionalCoreStartable(monitor) {
private var currentRegisteredState = false
@@ -90,7 +91,7 @@ constructor(
}
if (
- Flags.communalHubOnMobile() &&
+ communalSettingsInteractor.isV2FlagEnabled() &&
packageManager.getComponentEnabledSetting(overlayServiceComponent) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
) {
@@ -111,7 +112,7 @@ constructor(
}
// Enable for hub on mobile
- if (Flags.communalHubOnMobile()) {
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
// Not available on TV or auto
if (
packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) ||
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 43b7cedcd767..571b37f43fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -18,7 +18,6 @@ package com.android.systemui.dreams;
import static android.service.dreams.Flags.dreamWakeRedirect;
-import static com.android.systemui.Flags.communalHubOnMobile;
import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
@@ -60,11 +59,13 @@ import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.shared.log.CommunalUiEvent;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.communal.shared.model.CommunalTransitionKeys;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
@@ -141,8 +142,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
*/
private boolean mBouncerShowing = false;
- private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- mDreamComplicationComponentFactory;
+ private final DreamComplicationComponent.Factory mDreamComplicationComponentFactory;
private final ComplicationComponent.Factory mComplicationComponentFactory;
private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
@@ -171,6 +171,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private final SceneInteractor mSceneInteractor;
private final CommunalInteractor mCommunalInteractor;
+ private final CommunalSettingsInteractor mCommunalSettingsInteractor;
private boolean mCommunalAvailable;
@@ -376,14 +377,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Main DelayableExecutor executor,
ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
ComplicationComponent.Factory complicationComponentFactory,
- com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- dreamComplicationComponentFactory,
+ DreamComplicationComponent.Factory dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
AmbientTouchComponent.Factory ambientTouchComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
ScrimManager scrimManager,
CommunalInteractor communalInteractor,
+ CommunalSettingsInteractor communalSettingsInteractor,
SceneInteractor sceneInteractor,
SystemDialogsCloser systemDialogsCloser,
UiEventLogger uiEventLogger,
@@ -412,6 +413,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mDreamOverlayCallbackController = dreamOverlayCallbackController;
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
+ mCommunalSettingsInteractor = communalSettingsInteractor;
mSceneInteractor = sceneInteractor;
mSystemDialogsCloser = systemDialogsCloser;
mGestureInteractor = gestureInteractor;
@@ -479,9 +481,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mLifecycleOwner,
() -> mExecutor.execute(DreamOverlayService.this::requestExit),
new ViewModelStore(), mTouchInsetManager);
- final com.android.systemui.dreams.complication.dagger.ComplicationComponent
- dreamComplicationComponent = mDreamComplicationComponentFactory.create(
- complicationComponent.getVisibilityController(), mTouchInsetManager);
+ final DreamComplicationComponent dreamComplicationComponent =
+ mDreamComplicationComponentFactory.create(
+ complicationComponent.getVisibilityController(), mTouchInsetManager);
final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
@@ -489,7 +491,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
final ArrayList<TouchHandler> touchHandlers = new ArrayList<>(
List.of(dreamComplicationComponent.getHideComplicationTouchHandler()));
- if (!communalHubOnMobile()) {
+ if (!mCommunalSettingsInteractor.isV2FlagEnabled()) {
// Do not add the communal touch handler for glanceable hub v2 since there is no dream
// to hub swipe gesture.
touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler());
@@ -576,7 +578,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
} else {
mCommunalInteractor.changeScene(CommunalScenes.Communal,
"dream wake requested",
- communalHubOnMobile() ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null);
+ mCommunalSettingsInteractor.isV2FlagEnabled()
+ ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index f8ae5c28d018..ea5fbc6fa0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -17,8 +17,8 @@
package com.android.systemui.dreams.complication;
import static com.android.systemui.Flags.removeDreamOverlayHideOnTouch;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
import android.util.Log;
import android.view.MotionEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
index 492c50255b18..17d3acd893e1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
@@ -6,15 +6,15 @@ import com.android.systemui.touch.TouchInsetManager
import dagger.BindsInstance
import dagger.Subcomponent
-@Subcomponent(modules = [ComplicationModule::class])
-interface ComplicationComponent {
- /** Factory for generating [ComplicationComponent]. */
+@Subcomponent(modules = [DreamComplicationModule::class])
+interface DreamComplicationComponent {
+ /** Factory for generating [DreamComplicationComponent]. */
@Subcomponent.Factory
interface Factory {
fun create(
@BindsInstance visibilityController: Complication.VisibilityController,
- @BindsInstance touchInsetManager: TouchInsetManager
- ): ComplicationComponent
+ @BindsInstance touchInsetManager: TouchInsetManager,
+ ): DreamComplicationComponent
}
fun getHideComplicationTouchHandler(): HideComplicationTouchHandler
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
index 6fd6f4e3d4eb..59af22a6636f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
@@ -1,14 +1,14 @@
package com.android.systemui.dreams.complication.dagger
import android.content.res.Resources
-import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import javax.inject.Named
@Module
-object ComplicationModule {
+object DreamComplicationModule {
const val COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"
const val COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3171bbc6d6b0..216cb86f8865 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -32,7 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.SystemDialogsCloser;
-import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
import com.android.systemui.dreams.homecontrols.dagger.HomeControlsDataSourceModule;
import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent;
@@ -68,7 +68,7 @@ import javax.inject.Named;
HomeControlsDataSourceModule.class,
},
subcomponents = {
- ComplicationComponent.class,
+ DreamComplicationComponent.class,
DreamOverlayComponent.class,
HomeControlsRemoteServiceComponent.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 7242770e72e5..e2646353bc19 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -21,6 +21,9 @@ import com.android.systemui.CoreStartable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.ContextualEducationMetricsLogger
@@ -37,6 +40,7 @@ import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.time.Clock
+import java.time.Instant
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
@@ -48,6 +52,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.merge
@@ -71,6 +76,8 @@ constructor(
const val TAG = "KeyboardTouchpadEduInteractor"
const val MAX_SIGNAL_COUNT: Int = 2
const val MAX_EDUCATION_SHOW_COUNT: Int = 2
+ const val MAX_TOAST_PER_USAGE_SESSION: Int = 2
+
val usageSessionDuration =
getDurationForConfig("persist.contextual_edu.usage_session_sec", 3.days)
val minIntervalBetweenEdu =
@@ -110,6 +117,16 @@ constructor(
awaitClose { overviewProxyService.removeCallback(listener) }
}
+ private val gestureModelMap: Flow<Map<GestureType, GestureEduModel>> =
+ combine(
+ contextualEducationInteractor.backGestureModelFlow,
+ contextualEducationInteractor.homeGestureModelFlow,
+ contextualEducationInteractor.overviewGestureModelFlow,
+ contextualEducationInteractor.allAppsGestureModelFlow,
+ ) { back, home, overview, allApps ->
+ mapOf(BACK to back, HOME to home, OVERVIEW to overview, ALL_APPS to allApps)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
override fun start() {
backgroundScope.launch {
@@ -211,7 +228,11 @@ constructor(
private suspend fun incrementSignalCount(gestureType: GestureType) {
val targetDevice = getTargetDevice(gestureType)
- if (isTargetDeviceConnected(targetDevice) && hasInitialDelayElapsed(targetDevice)) {
+ if (
+ isTargetDeviceConnected(targetDevice) &&
+ hasInitialDelayElapsed(targetDevice) &&
+ isMinIntervalForToastEduElapsed(gestureType)
+ ) {
contextualEducationInteractor.incrementSignalCount(gestureType)
}
}
@@ -223,6 +244,28 @@ constructor(
}
}
+ private suspend fun isMinIntervalForToastEduElapsed(gestureType: GestureType): Boolean {
+ val gestureModelMap = gestureModelMap.first()
+ // Only perform checking if the next edu is toast (i.e. no education is shown yet)
+ if (gestureModelMap[gestureType]?.educationShownCount != 0) {
+ return true
+ }
+
+ val wasLastEduToast = { gesture: GestureEduModel -> gesture.educationShownCount == 1 }
+ val toastEduTimesInCurrentSession: List<Instant> =
+ gestureModelMap.values
+ .filter { wasLastEduToast(it) }
+ .mapNotNull { it.lastEducationTime }
+ .filter { it >= clock.instant().minusSeconds(usageSessionDuration.inWholeSeconds) }
+
+ return if (toastEduTimesInCurrentSession.size >= MAX_TOAST_PER_USAGE_SESSION) {
+ val lastToastTime: Instant? = toastEduTimesInCurrentSession.maxOrNull()
+ clock.instant().isAfter(lastToastTime?.plusSeconds(usageSessionDuration.inWholeSeconds))
+ } else {
+ true
+ }
+ }
+
/**
* Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would
* be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index c49ba80c660b..a36e45f43bef 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -29,6 +29,11 @@ import android.view.accessibility.AccessibilityManager
import androidx.core.app.NotificationCompat
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.ui.viewmodel.ContextualEduNotificationViewModel
@@ -37,6 +42,10 @@ import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -59,6 +68,8 @@ constructor(
private const val CHANNEL_ID = "ContextualEduNotificationChannel"
private const val TAG = "ContextualEduUiCoordinator"
private const val NOTIFICATION_ID = 1000
+ private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ private const val SYSTEMUI_PACKAGE_NAME: String = "com.android.systemui"
}
@Inject
@@ -125,7 +136,7 @@ constructor(
.setSmallIcon(R.drawable.ic_settings)
.setContentTitle(model.title)
.setContentText(model.message)
- .setContentIntent(createPendingIntent())
+ .setContentIntent(createPendingIntent(model.gestureType))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
.addExtras(extras)
@@ -138,21 +149,37 @@ constructor(
)
}
- private fun createPendingIntent(): PendingIntent {
+ private fun createPendingIntent(gestureType: GestureType): PendingIntent {
val intent =
- Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
- addCategory(Intent.CATEGORY_DEFAULT)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- putExtra(
- INTENT_TUTORIAL_ENTRY_POINT_KEY,
- INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU,
- )
+ when (gestureType) {
+ BACK -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK)
+ HOME -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME)
+ ALL_APPS -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_KEYBOARD)
+ OVERVIEW -> createTouchpadTutorialIntent()
}
+
return PendingIntent.getActivity(
context,
/* requestCode= */ 0,
intent,
- PendingIntent.FLAG_IMMUTABLE,
+ // FLAG_UPDATE_CURRENT to avoid caching of intent extras and always use latest values
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
+
+ private fun createKeyboardTouchpadTutorialIntent(tutorialType: String): Intent {
+ return Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
+ addCategory(Intent.CATEGORY_DEFAULT)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(INTENT_TUTORIAL_SCOPE_KEY, tutorialType)
+ putExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY, INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU)
+ }
+ }
+
+ private fun createTouchpadTutorialIntent(): Intent {
+ return Intent(TUTORIAL_ACTION).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ setPackage(SYSTEMUI_PACKAGE_NAME)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
index 5a02cda8c3f5..06c0d6c88f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.education.ui.viewmodel
+import com.android.systemui.contextualeducation.GestureType
+
sealed class ContextualEduContentViewModel(open val userId: Int)
data class ContextualEduNotificationViewModel(
val title: String,
val message: String,
+ val gestureType: GestureType,
override val userId: Int,
) : ContextualEduContentViewModel(userId)
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 7417a7098ea3..443ad020a570 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -68,6 +68,7 @@ constructor(
ContextualEduNotificationViewModel(
getEduTitle(it),
getEduContent(it),
+ it.gestureType,
it.userId,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
index 215ceacaef14..0ed4007d68ef 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -78,9 +78,16 @@ fun Expandable.withStateAwareness(
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? =
delegate
- .activityTransitionController(launchCujType, cookie, component, returnCujType)
+ .activityTransitionController(
+ launchCujType,
+ cookie,
+ component,
+ returnCujType,
+ isEphemeral,
+ )
?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
override fun dialogTransitionController(
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index b82aa817afd8..1504402279b4 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -284,6 +284,7 @@ constructor(
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
val delegatedController =
ActivityTransitionAnimator.Controller.fromView(
@@ -292,6 +293,7 @@ constructor(
cookie,
component,
returnCujType,
+ isEphemeral,
)
return delegatedController?.let { createTransitionControllerDelegate(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 1b044de5cf63..0c1bc835517a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -20,6 +20,7 @@ import android.content.res.Configuration
import androidx.annotation.RawRes
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -33,8 +34,12 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
@@ -125,6 +130,8 @@ fun TutorialDescription(
config: TutorialScreenConfig,
modifier: Modifier = Modifier,
) {
+ val focusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) { focusRequester.requestFocus() }
val (titleTextId, bodyTextId) =
if (actionState is Finished) {
config.strings.titleSuccessResId to config.strings.bodySuccessResId
@@ -136,6 +143,7 @@ fun TutorialDescription(
text = stringResource(id = titleTextId),
style = MaterialTheme.typography.displayLarge,
color = config.colors.title,
+ modifier = Modifier.focusRequester(focusRequester).focusable(),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index 2be619bac998..ad18817704aa 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -23,16 +23,22 @@ import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.Ref
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.util.lerp
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.compose.LottieAnimation
@@ -44,6 +50,7 @@ import com.airbnb.lottie.compose.rememberLottieComposition
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
@Composable
fun TutorialAnimation(
@@ -93,13 +100,23 @@ private fun EducationAnimation(
animationProperties: LottieDynamicProperties,
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
+ var isPlaying by remember { mutableStateOf(true) }
val progress by
- animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+ animateLottieCompositionAsState(
+ composition,
+ iterations = LottieConstants.IterateForever,
+ isPlaying = isPlaying,
+ restartOnPlay = false,
+ )
+ val animationDescription = stringResource(R.string.tutorial_animation_content_description)
LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = animationProperties,
- modifier = Modifier.fillMaxSize(),
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable { isPlaying = !isPlaying }
+ .semantics { contentDescription = animationDescription },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
index 38fc2a80ad02..84a423e226b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -21,11 +21,12 @@ import android.view.Surface
import android.view.WindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.Utils
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
-import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,9 +38,9 @@ class KeyboardDockingIndicationViewModel
@Inject
constructor(
private val windowManager: WindowManager,
- private val context: Context,
+ @Application private val context: Context,
keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
- @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+ @GlobalConfig configurationInteractor: ConfigurationInteractor,
@Background private val backgroundScope: CoroutineScope,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
index 8c393e27da59..3020e5dedd17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
@@ -21,6 +21,7 @@ import android.hardware.input.InputGestureData
import android.view.KeyboardShortcutGroup
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
/**
* Internal Keyboard Shortcut models to use with [ShortcutCategoriesUtils.fetchShortcutCategory]
@@ -55,3 +56,8 @@ data class InternalKeyboardShortcutInfo(
val icon: Icon? = null,
val isCustomShortcut: Boolean = false,
)
+
+data class InternalGroupsSource(
+ val groups: List<InternalKeyboardShortcutGroup>,
+ val type: ShortcutCategoryType,
+) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
new file mode 100644
index 000000000000..36cd40052041
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.InputSettings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS
+import com.android.systemui.settings.UserTracker
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+class CustomInputGesturesRepository
+@Inject
+constructor(private val userTracker: UserTracker,
+ @Background private val bgCoroutineContext: CoroutineContext)
+{
+
+ private val userContext: Context
+ get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+ // Input manager created with user context to provide correct user id when requesting custom
+ // shortcut
+ private val inputManager: InputManager
+ get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+
+ private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList())
+
+ val customInputGestures =
+ _customInputGesture.onStart { refreshCustomInputGestures() }
+
+ private fun refreshCustomInputGestures() {
+ setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
+ }
+
+ private fun setCustomInputGestures(inputGestures: List<InputGestureData>) {
+ _customInputGesture.value = inputGestures
+ }
+
+ fun retrieveCustomInputGestures(): List<InputGestureData> {
+ return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ } else emptyList()
+ }
+
+ suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ when (val result = inputManager.addCustomInputGesture(inputGesture)) {
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
+ refreshCustomInputGestures()
+ SUCCESS
+ }
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
+ ERROR_RESERVED_COMBINATION
+
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
+ ERROR_RESERVED_COMBINATION
+
+ else -> {
+ Log.w(
+ TAG,
+ "Attempted to add inputGesture: $inputGesture " +
+ "but ran into an error with code: $result",
+ )
+ ERROR_OTHER
+ }
+ }
+ }
+ }
+
+ suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext){
+ when (
+ val result = inputManager.removeCustomInputGesture(inputGesture)
+ ) {
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
+ refreshCustomInputGestures()
+ SUCCESS
+ }
+ else -> {
+ Log.w(
+ TAG,
+ "Attempted to delete inputGesture: $inputGesture " +
+ "but ran into an error with code: $result",
+ )
+ ERROR_OTHER
+ }
+ }
+ }
+ }
+
+ suspend fun resetAllCustomInputGestures(): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ try {
+ inputManager.removeAllCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ setCustomInputGestures(emptyList())
+ SUCCESS
+ } catch (e: Exception) {
+ Log.w(
+ TAG,
+ "Attempted to remove all custom shortcut but ran into a remote error: $e",
+ )
+ ERROR_OTHER
+ }
+ }
+ }
+
+ private companion object {
+ private const val TAG = "CustomInputGesturesRepository"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 321fd57d3e8b..8afec04a621c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -16,18 +16,10 @@
package com.android.systemui.keyboard.shortcut.data.repository
-import android.content.Context
-import android.content.Context.INPUT_SERVICE
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.Builder
-import android.hardware.input.InputGestureData.KeyTrigger
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
-import android.hardware.input.InputSettings
-import android.hardware.input.KeyGestureEvent
import android.hardware.input.KeyGestureEvent.KeyGestureType
import android.util.Log
import androidx.annotation.VisibleForTesting
@@ -36,17 +28,11 @@ import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
-import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
-import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
-import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -55,27 +41,21 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
@SysUISingleton
class CustomShortcutCategoriesRepository
@Inject
constructor(
stateRepository: ShortcutHelperStateRepository,
- private val userTracker: UserTracker,
@Background private val backgroundScope: CoroutineScope,
@Background private val bgCoroutineContext: CoroutineContext,
private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
- private val context: Context,
- private val inputGestureMaps: InputGestureMaps,
-) : ShortcutCategoriesRepository {
-
- private val userContext: Context
- get() = userTracker.createCurrentUserContext(userTracker.userContext)
-
- // Input manager created with user context to provide correct user id when requesting custom
- // shortcut
+ private val inputGestureDataAdapter: InputGestureDataAdapter,
+ private val customInputGesturesRepository: CustomInputGesturesRepository,
private val inputManager: InputManager
- get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+) : ShortcutCategoriesRepository {
private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null)
private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
@@ -125,14 +105,12 @@ constructor(
)
override val categories: Flow<List<ShortcutCategory>> =
- activeInputDevice
- .map { inputDevice ->
+ combine(activeInputDevice, customInputGesturesRepository.customInputGestures)
+ { inputDevice, inputGestures ->
if (inputDevice == null) {
emptyList()
} else {
- val customInputGesturesForUser: List<InputGestureData> =
- getCustomInputGestures()
- val sources = toInternalGroupSources(customInputGesturesForUser)
+ val sources = inputGestureDataAdapter.toInternalGroupSources(inputGestures)
val supportedKeyCodes =
shortcutCategoriesUtils.fetchSupportedKeyCodes(
inputDevice.id,
@@ -181,56 +159,28 @@ constructor(
private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? {
val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel()
- return getCustomInputGestures().firstOrNull { it.action.keyGestureType() == keyGestureType }
+ return customInputGesturesRepository.retrieveCustomInputGestures()
+ .firstOrNull { it.action.keyGestureType() == keyGestureType }
}
suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
ShortcutCustomizationRequestResult {
- return withContext(bgCoroutineContext) {
- val inputGestureData =
- buildInputGestureDataForShortcutBeingCustomized()
- ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER
-
- return@withContext when (inputManager.addCustomInputGesture(inputGestureData)) {
- CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
- CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
- ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
+ val inputGestureData =
+ buildInputGestureDataForShortcutBeingCustomized()
+ ?: return ShortcutCustomizationRequestResult.ERROR_OTHER
- CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
- ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
-
- else -> ShortcutCustomizationRequestResult.ERROR_OTHER
- }
- }
+ return customInputGesturesRepository.addCustomInputGesture(inputGestureData)
}
- suspend fun deleteShortcutCurrentlyBeingCustomized():
- ShortcutCustomizationRequestResult {
- return withContext(bgCoroutineContext) {
- val inputGestureData =
- retrieveInputGestureDataForShortcutBeingDeleted()
- ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER
- return@withContext when (
- val result = inputManager.removeCustomInputGesture(inputGestureData)
- ) {
- CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
- else -> {
- Log.w(
- TAG,
- "Attempted to delete shortcut being customized " +
- "${_shortcutBeingCustomized.value} but ran into an error. InputGestureData" +
- " = $inputGestureData, error code: $result",
- )
- ShortcutCustomizationRequestResult.ERROR_OTHER
- }
- }
- }
+ suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ val inputGestureData =
+ retrieveInputGestureDataForShortcutBeingDeleted()
+ ?: return ShortcutCustomizationRequestResult.ERROR_OTHER
+ return customInputGesturesRepository.deleteCustomInputGesture(inputGestureData)
}
- private fun getCustomInputGestures(): List<InputGestureData> {
- return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
- inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
- } else emptyList()
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customInputGesturesRepository.resetAllCustomInputGestures()
}
private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
@@ -260,7 +210,8 @@ constructor(
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
@KeyGestureType
@@ -276,7 +227,8 @@ constructor(
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
@@ -305,74 +257,6 @@ constructor(
return _shortcutBeingCustomized.value
}
- private fun toInternalGroupSources(
- inputGestures: List<InputGestureData>
- ): List<InternalGroupsSource> {
- val ungroupedInternalGroupSources =
- inputGestures.mapNotNull { gestureData ->
- val keyTrigger = gestureData.trigger as KeyTrigger
- val keyGestureType = gestureData.action.keyGestureType()
- fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
- toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
- internalKeyboardShortcutInfo ->
- val group =
- InternalKeyboardShortcutGroup(
- label = groupLabel,
- items = listOf(internalKeyboardShortcutInfo),
- )
-
- fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
- InternalGroupsSource(groups = listOf(group), type = it)
- }
- }
- }
- }
-
- return ungroupedInternalGroupSources
- }
-
- private fun toInternalKeyboardShortcutInfo(
- keyGestureType: Int,
- keyTrigger: KeyTrigger,
- ): InternalKeyboardShortcutInfo? {
- fetchShortcutInfoLabelByGestureType(keyGestureType)?.let {
- return InternalKeyboardShortcutInfo(
- label = it,
- keycode = keyTrigger.keycode,
- modifiers = keyTrigger.modifierState,
- isCustomShortcut = true,
- )
- }
- return null
- }
-
- private fun fetchGroupLabelByGestureType(
- @KeyGestureEvent.KeyGestureType keyGestureType: Int
- ): String? {
- inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
- return context.getString(it)
- } ?: return null
- }
-
- private fun fetchShortcutInfoLabelByGestureType(
- @KeyGestureEvent.KeyGestureType keyGestureType: Int
- ): String? {
- inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
- return context.getString(it)
- } ?: return null
- }
-
- private fun fetchShortcutCategoryTypeByGestureType(
- @KeyGestureEvent.KeyGestureType keyGestureType: Int
- ): ShortcutCategoryType? {
- return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
- }
-
- private data class InternalGroupsSource(
- val groups: List<InternalKeyboardShortcutGroup>,
- val type: ShortcutCategoryType,
- )
-
private companion object {
private const val TAG = "CustomShortcutCategoriesRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
new file mode 100644
index 000000000000..df7101e21cce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.annotation.SuppressLint
+import android.app.role.RoleManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_MAIN
+import android.content.Intent.CATEGORY_LAUNCHER
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
+import android.content.pm.PackageManager.NameNotFoundException
+import android.graphics.drawable.Icon
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.CategoryData
+import android.hardware.input.AppLaunchData.ComponentData
+import android.hardware.input.AppLaunchData.RoleData
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.KeyTrigger
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+import android.hardware.input.KeyGestureEvent.KeyGestureType
+import android.util.Log
+import com.android.internal.app.ResolverActivity
+import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
+import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+
+
+class InputGestureDataAdapter
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ private val inputGestureMaps: InputGestureMaps,
+ private val context: Context
+) {
+ private val userContext: Context
+ get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+ fun toInternalGroupSources(
+ inputGestures: List<InputGestureData>
+ ): List<InternalGroupsSource> {
+ val ungroupedInternalGroupSources =
+ inputGestures.mapNotNull { gestureData ->
+ val keyTrigger = gestureData.trigger as KeyTrigger
+ val keyGestureType = gestureData.action.keyGestureType()
+ val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData()
+ fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
+ toInternalKeyboardShortcutInfo(
+ keyGestureType,
+ keyTrigger,
+ appLaunchData
+ )?.let { internalKeyboardShortcutInfo ->
+ val group =
+ InternalKeyboardShortcutGroup(
+ label = groupLabel,
+ items = listOf(internalKeyboardShortcutInfo),
+ )
+
+ fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
+ InternalGroupsSource(groups = listOf(group), type = it)
+ }
+ }
+ }
+ }
+
+ return ungroupedInternalGroupSources
+ }
+
+ fun getKeyGestureTypeFromShortcutLabel(label: String): Int? {
+ return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[label]
+ }
+
+ private fun toInternalKeyboardShortcutInfo(
+ keyGestureType: Int,
+ keyTrigger: KeyTrigger,
+ appLaunchData: AppLaunchData?,
+ ): InternalKeyboardShortcutInfo? {
+ fetchShortcutLabelByGestureType(keyGestureType, appLaunchData)?.let {
+ return InternalKeyboardShortcutInfo(
+ label = it,
+ keycode = keyTrigger.keycode,
+ modifiers = keyTrigger.modifierState,
+ isCustomShortcut = true,
+ icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) }
+ )
+ }
+ return null
+ }
+
+ @SuppressLint("QueryPermissionsNeeded")
+ private fun fetchShortcutIconByAppLaunchData(
+ appLaunchData: AppLaunchData
+ ): Icon? {
+ val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null
+ val resolvedActivity = resolveSingleMatchingActivityFrom(intent)
+
+ return if (resolvedActivity == null) {
+ null
+ } else {
+ Icon.createWithResource(context, resolvedActivity.iconResource)
+ }
+ }
+
+ private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
+ inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
+ return context.getString(it)
+ } ?: return null
+ }
+
+ private fun fetchShortcutLabelByGestureType(
+ @KeyGestureType keyGestureType: Int,
+ appLaunchData: AppLaunchData?
+ ): String? {
+ inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
+ return context.getString(it)
+ }
+
+ if (keyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+ return fetchShortcutLabelByAppLaunchData(appLaunchData!!)
+ }
+
+ return null
+ }
+
+ private fun fetchShortcutLabelByAppLaunchData(appLaunchData: AppLaunchData): String? {
+ val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null
+ val resolvedActivity = resolveSingleMatchingActivityFrom(intent)
+
+ return if (resolvedActivity == null) {
+ getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next())
+ } else resolvedActivity.loadLabel(userContext.packageManager).toString()
+
+ }
+
+ @SuppressLint("QueryPermissionsNeeded")
+ private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? {
+ val packageManager = userContext.packageManager
+ val resolvedActivity = intent.resolveActivityInfo(
+ packageManager,
+ /* flags= */ MATCH_DEFAULT_ONLY
+ ) ?: return null
+
+ val matchesMultipleActivities =
+ ResolverActivity::class.qualifiedName.equals(resolvedActivity.name)
+
+ return if (matchesMultipleActivities) {
+ return null
+ } else resolvedActivity
+ }
+
+ private fun getIntentCategoryLabel(category: String?): String? {
+ val categoryLabelRes = when (category.toString()) {
+ Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser
+ Intent.CATEGORY_APP_CONTACTS -> R.string.keyboard_shortcut_group_applications_contacts
+ Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email
+ Intent.CATEGORY_APP_CALENDAR -> R.string.keyboard_shortcut_group_applications_calendar
+ Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps
+ Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music
+ Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms
+ Intent.CATEGORY_APP_CALCULATOR -> R.string.keyboard_shortcut_group_applications_calculator
+ else -> {
+ Log.w(TAG, ("No label for app category $category"))
+ null
+ }
+ }
+
+ return if (categoryLabelRes == null){
+ return null
+ } else {
+ context.getString(categoryLabelRes)
+ }
+ }
+
+ private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? {
+ return when (appLaunchData) {
+ is CategoryData -> Intent.makeMainSelectorActivity(
+ /* selectorAction= */ ACTION_MAIN,
+ /* selectorCategory= */ appLaunchData.category
+ )
+
+ is RoleData -> getRoleLaunchIntent(appLaunchData.role)
+ is ComponentData -> resolveComponentNameIntent(
+ packageName = appLaunchData.packageName,
+ className = appLaunchData.className
+ )
+
+ else -> null
+ }
+ }
+
+ private fun resolveComponentNameIntent(packageName: String, className: String): Intent? {
+ buildIntentFromComponentName(ComponentName(packageName, className))?.let { return it }
+ buildIntentFromComponentName(ComponentName(
+ userContext.packageManager.canonicalToCurrentPackageNames(arrayOf(packageName))[0],
+ className
+ ))?.let { return it }
+ return null
+ }
+
+ private fun buildIntentFromComponentName(componentName: ComponentName): Intent? {
+ try{
+ val flags =
+ MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES
+ // attempt to retrieve activity info to see if a NameNotFoundException is thrown.
+ userContext.packageManager.getActivityInfo(componentName, flags)
+ } catch (e: NameNotFoundException) {
+ Log.w(
+ TAG,
+ "Unable to find activity info for componentName: $componentName"
+ )
+ return null
+ }
+
+ return Intent(ACTION_MAIN).apply {
+ addCategory(CATEGORY_LAUNCHER)
+ component = componentName
+ }
+ }
+
+ @SuppressLint("NonInjectedService")
+ private fun getRoleLaunchIntent(role: String): Intent? {
+ val packageManager = userContext.packageManager
+ val roleManager = userContext.getSystemService(RoleManager::class.java)!!
+ if (roleManager.isRoleAvailable(role)) {
+ roleManager.getDefaultApplication(role)?.let { rolePackage ->
+ packageManager.getLaunchIntentForPackage(rolePackage)?.let { return it }
+ ?: Log.w(TAG, "No launch intent for role $role")
+ } ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}")
+ } else {
+ Log.w(TAG, "Role $role is not available.")
+ }
+ return null
+ }
+
+ private fun fetchShortcutCategoryTypeByGestureType(
+ @KeyGestureType keyGestureType: Int
+ ): ShortcutCategoryType? {
+ return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
+ }
+
+ private companion object {
+ private const val TAG = "InputGestureDataUtils"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index ecc076178d2d..30a2f330edf6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -22,14 +22,8 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
@@ -74,13 +68,7 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking,
// App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to AppCategories,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories,
)
val gestureToInternalKeyboardShortcutGroupLabelResIdMap =
@@ -104,7 +92,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.shortcut_helper_category_system_apps,
// Multitasking Category
- KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.shortcutHelper_category_recent_apps,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
R.string.shortcutHelper_category_split_screen,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
@@ -117,20 +104,14 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.shortcutHelper_category_split_screen,
// App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
- R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to R.string.keyboard_shortcut_group_applications,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION to
R.string.keyboard_shortcut_group_applications,
)
+ /**
+ * App Category shortcut labels are mapped dynamically based on intent
+ * see [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData]
+ */
val gestureToInternalKeyboardShortcutInfoLabelResIdMap =
mapOf(
// System Category
@@ -159,22 +140,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.system_multitasking_splitscreen_focus_lhs,
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
R.string.system_multitasking_splitscreen_focus_rhs,
-
- // App Category
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
- R.string.keyboard_shortcut_group_applications_calculator,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
- R.string.keyboard_shortcut_group_applications_calendar,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
- R.string.keyboard_shortcut_group_applications_browser,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
- R.string.keyboard_shortcut_group_applications_contacts,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to
- R.string.keyboard_shortcut_group_applications_email,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to
- R.string.keyboard_shortcut_group_applications_maps,
- KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
- R.string.keyboard_shortcut_group_applications_sms,
)
val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index a0897f293624..4a725ec8abad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperExclusions
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
@@ -46,6 +47,7 @@ constructor(
private val context: Context,
@Background private val backgroundCoroutineContext: CoroutineContext,
private val inputManager: InputManager,
+ private val shortcutHelperExclusions: ShortcutHelperExclusions,
) {
fun removeUnsupportedModifiers(modifierMask: Int): Int {
@@ -116,7 +118,10 @@ constructor(
.filter {
// Allow KEYCODE_UNKNOWN (0) because shortcuts can have just modifiers and no
// keycode, or they could have a baseCharacter instead of a keycode.
- it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
+ it.keycode == KeyEvent.KEYCODE_UNKNOWN ||
+ supportedKeyCodes.contains(it.keycode) ||
+ // Support keyboard function row key codes
+ keyGlyphMap?.functionRowKeys?.contains(it.keycode) ?: false
}
.mapNotNull { toShortcut(keyGlyphMap, keyCharacterMap, it, keepIcons) }
@@ -132,6 +137,8 @@ constructor(
label = shortcutInfo.label,
icon = toShortcutIcon(keepIcon, shortcutInfo),
commands = listOf(shortcutCommand),
+ isCustomizable =
+ shortcutHelperExclusions.isShortcutCustomizable(shortcutInfo.label),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index e47b33f8c37c..d91922ddb65e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -99,6 +99,7 @@ import android.view.KeyEvent.KEYCODE_PAGE_DOWN
import android.view.KeyEvent.KEYCODE_PAGE_UP
import android.view.KeyEvent.KEYCODE_PERIOD
import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_SCREENSHOT
import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
@@ -125,6 +126,14 @@ object ShortcutHelperKeys {
KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
)
+ val keyLabelResIds =
+ mapOf(
+ KEYCODE_BACK to R.string.group_system_go_back,
+ KEYCODE_HOME to R.string.group_system_access_home_screen,
+ KEYCODE_RECENT_APPS to R.string.group_system_overview_open_apps,
+ KEYCODE_SCREENSHOT to R.string.group_system_full_screenshot,
+ )
+
val modifierLabels =
mapOf<Int, (Context) -> String>(
// Modifiers
@@ -140,6 +149,7 @@ object ShortcutHelperKeys {
mapOf<Int, (Context) -> String>(
KEYCODE_HOME to { context -> context.getString(R.string.keyboard_key_home) },
KEYCODE_BACK to { context -> context.getString(R.string.keyboard_key_back) },
+ KEYCODE_RECENT_APPS to { context -> context.getString(R.string.accessibility_recent) },
KEYCODE_DPAD_UP to { context -> context.getString(R.string.keyboard_key_dpad_up) },
KEYCODE_DPAD_DOWN to { context -> context.getString(R.string.keyboard_key_dpad_down) },
KEYCODE_DPAD_LEFT to { context -> context.getString(R.string.keyboard_key_dpad_left) },
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
index 1b2098650278..4787507dac39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
@@ -17,12 +17,16 @@
package com.android.systemui.keyboard.shortcut.data.source
import android.content.res.Resources
+import android.hardware.input.InputManager
+import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
import android.view.KeyEvent.KEYCODE_SPACE
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
import android.view.WindowManager
import android.view.WindowManager.KeyboardShortcutsReceiver
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
import com.android.systemui.res.R
@@ -31,16 +35,19 @@ import kotlinx.coroutines.suspendCancellableCoroutine
class InputShortcutsSource
@Inject
-constructor(@Main private val resources: Resources, private val windowManager: WindowManager) :
- KeyboardShortcutGroupsSource {
+constructor(
+ @Main private val resources: Resources,
+ private val windowManager: WindowManager,
+ private val inputManager: InputManager,
+) : KeyboardShortcutGroupsSource {
override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
- getInputLanguageShortcutGroup() + getImeShortcutGroup(deviceId)
+ getInputLanguageShortcutGroup(deviceId) + getImeShortcutGroup(deviceId)
- private fun getInputLanguageShortcutGroup() =
+ private fun getInputLanguageShortcutGroup(deviceId: Int) =
listOf(
KeyboardShortcutGroup(
resources.getString(R.string.shortcut_helper_category_input),
- inputLanguageShortcuts()
+ inputLanguageShortcuts() + hardwareShortcuts(deviceId),
)
)
@@ -53,9 +60,23 @@ constructor(@Main private val resources: Resources, private val windowManager: W
/* Switch previous language (next language): Ctrl + Shift + Space */
shortcutInfo(resources.getString(R.string.input_switch_input_language_previous)) {
command(META_CTRL_ON or META_SHIFT_ON, KEYCODE_SPACE)
- }
+ },
)
+ private fun hardwareShortcuts(deviceId: Int): List<KeyboardShortcutInfo> {
+ if (shortcutHelperKeyGlyph()) {
+ val keyGlyphMap = inputManager.getKeyGlyphMap(deviceId)
+ if (keyGlyphMap != null && keyGlyphMap.functionRowKeys.contains(KEYCODE_EMOJI_PICKER)) {
+ return listOf(
+ shortcutInfo(resources.getString(R.string.input_access_emoji)) {
+ command(modifiers = 0, KEYCODE_EMOJI_PICKER)
+ }
+ )
+ }
+ }
+ return emptyList()
+ }
+
private suspend fun getImeShortcutGroup(deviceId: Int): List<KeyboardShortcutGroup> =
suspendCancellableCoroutine { continuation ->
val shortcutsReceiver = KeyboardShortcutsReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index 0201f402af2a..df6b04e2afd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -16,36 +16,40 @@
package com.android.systemui.keyboard.shortcut.data.source
+import android.content.Context
import android.content.res.Resources
import android.view.KeyEvent.KEYCODE_D
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
import android.view.KeyEvent.KEYCODE_DPAD_UP
-import android.view.KeyEvent.KEYCODE_TAB
+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_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
import com.android.systemui.res.R
import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
+import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import javax.inject.Inject
-class MultitaskingShortcutsSource @Inject constructor(@Main private val resources: Resources) :
+class MultitaskingShortcutsSource
+@Inject
+constructor(@Main private val resources: Resources, @Application private val context: Context) :
KeyboardShortcutGroupsSource {
override suspend fun shortcutGroups(deviceId: Int) =
listOf(
KeyboardShortcutGroup(
- resources.getString(R.string.shortcutHelper_category_recent_apps),
- recentsShortcuts(),
- ),
- KeyboardShortcutGroup(
resources.getString(R.string.shortcutHelper_category_split_screen),
splitScreenShortcuts(),
- ),
+ )
)
private fun splitScreenShortcuts() = buildList {
@@ -95,19 +99,39 @@ class MultitaskingShortcutsSource @Inject constructor(@Main private val resource
}
)
}
+ if (
+ DesktopModeStatus.canEnterDesktopMode(context) && enableTaskResizingKeyboardShortcuts()
+ ) {
+ // Snap a freeform window to the left
+ // - Meta + Left bracket
+ add(
+ shortcutInfo(resources.getString(R.string.system_desktop_mode_snap_left_window)) {
+ command(META_META_ON, KEYCODE_LEFT_BRACKET)
+ }
+ )
+ // Snap a freeform window to the right
+ // - Meta + Right bracket
+ add(
+ shortcutInfo(resources.getString(R.string.system_desktop_mode_snap_right_window)) {
+ command(META_META_ON, KEYCODE_RIGHT_BRACKET)
+ }
+ )
+ // Toggle maximize a freeform window
+ // - Meta + Equals
+ add(
+ shortcutInfo(
+ resources.getString(R.string.system_desktop_mode_toggle_maximize_window)
+ ) {
+ command(META_META_ON, KEYCODE_EQUALS)
+ }
+ )
+ // Minimize a freeform window
+ // - Meta + Minus
+ add(
+ shortcutInfo(resources.getString(R.string.system_desktop_mode_minimize_window)) {
+ command(META_META_ON, KEYCODE_MINUS)
+ }
+ )
+ }
}
-
- private fun recentsShortcuts() =
- listOf(
- // Cycle through recent apps (forward):
- // - Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
- command(META_ALT_ON, KEYCODE_TAB)
- },
- // Cycle through recent apps (back):
- // - Shift + Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
- command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
- },
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index 7c0c75ef2c4c..5060abdda247 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -17,11 +17,11 @@
package com.android.systemui.keyboard.shortcut.data.source
import android.content.res.Resources
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGlyphMap
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_BACK
-import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
-import android.view.KeyEvent.KEYCODE_ENTER
import android.view.KeyEvent.KEYCODE_ESCAPE
import android.view.KeyEvent.KEYCODE_H
import android.view.KeyEvent.KEYCODE_HOME
@@ -32,29 +32,92 @@ import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
import com.android.systemui.res.R
import javax.inject.Inject
-class SystemShortcutsSource @Inject constructor(@Main private val resources: Resources) :
+class SystemShortcutsSource
+@Inject
+constructor(@Main private val resources: Resources, private val inputManager: InputManager) :
KeyboardShortcutGroupsSource {
override suspend fun shortcutGroups(deviceId: Int) =
listOf(
KeyboardShortcutGroup(
resources.getString(R.string.shortcut_helper_category_system_controls),
- systemControlsShortcuts()
+ systemControlsShortcuts() + hardwareShortcuts(deviceId),
),
KeyboardShortcutGroup(
resources.getString(R.string.shortcut_helper_category_system_apps),
- systemAppsShortcuts()
- )
+ systemAppsShortcuts(),
+ ),
+ )
+
+ private fun hardwareShortcuts(deviceId: Int): List<KeyboardShortcutInfo> =
+ if (shortcutHelperKeyGlyph()) {
+ val keyGlyphMap = inputManager.getKeyGlyphMap(deviceId)
+ if (keyGlyphMap != null) {
+ functionRowKeys(keyGlyphMap) + keyCombinationShortcuts(keyGlyphMap)
+ } else {
+ // Not add function row keys if it is not supported by keyboard
+ emptyList()
+ }
+ } else {
+ defaultFunctionRowKeys()
+ }
+
+ private fun defaultFunctionRowKeys(): List<KeyboardShortcutInfo> =
+ listOf(
+ shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
+ command(modifiers = 0, KEYCODE_HOME)
+ },
+ shortcutInfo(resources.getString(R.string.group_system_go_back)) {
+ command(modifiers = 0, KEYCODE_BACK)
+ },
+ shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
+ command(modifiers = 0, KEYCODE_RECENT_APPS)
+ },
)
+ private fun functionRowKeys(keyGlyphMap: KeyGlyphMap): List<KeyboardShortcutInfo> {
+ val functionRowKeys = mutableListOf<KeyboardShortcutInfo>()
+ keyGlyphMap.functionRowKeys.forEach { keyCode ->
+ val labelResId = ShortcutHelperKeys.keyLabelResIds[keyCode]
+ if (labelResId != null) {
+ functionRowKeys.add(
+ shortcutInfo(resources.getString(labelResId)) {
+ command(modifiers = 0, keyCode)
+ }
+ )
+ }
+ }
+ return functionRowKeys
+ }
+
+ private fun keyCombinationShortcuts(keyGlyphMap: KeyGlyphMap): List<KeyboardShortcutInfo> {
+ val shortcuts = mutableListOf<KeyboardShortcutInfo>()
+ keyGlyphMap.hardwareShortcuts.forEach { (keyCombination, keyCode) ->
+ val labelResId = ShortcutHelperKeys.keyLabelResIds[keyCode]
+ if (labelResId != null) {
+ val info =
+ shortcutInfo(resources.getString(labelResId)) {
+ command(keyCombination.modifierState, keyCombination.keycode)
+ }
+ shortcuts.add(info)
+ }
+ }
+ return shortcuts
+ }
+
private fun systemControlsShortcuts() =
listOf(
// Access list of all apps and search (i.e. Search/Launcher):
@@ -63,42 +126,32 @@ class SystemShortcutsSource @Inject constructor(@Main private val resources: Res
command(META_META_ON)
},
// Access home screen:
- // - Home button
// - Meta + H
- // - Meta + Enter
- shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
- command(modifiers = 0, KEYCODE_HOME)
- },
shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
command(META_META_ON, KEYCODE_H)
},
- shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
- command(META_META_ON, KEYCODE_ENTER)
- },
// Overview of open apps:
- // - Recent apps button
// - Meta + Tab
shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
- command(modifiers = 0, KEYCODE_RECENT_APPS)
- },
- shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
command(META_META_ON, KEYCODE_TAB)
},
+ // Cycle through recent apps (forward):
+ // - Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
+ command(META_ALT_ON, KEYCODE_TAB)
+ },
+ // Cycle through recent apps (back):
+ // - Shift + Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
+ command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
+ },
// Back: go back to previous state (back button)
- // - Back button
// - Meta + Escape OR
- // - Meta + Backspace OR
// - Meta + Left arrow
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
- command(modifiers = 0, KEYCODE_BACK)
- },
- shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_ESCAPE)
},
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
- command(META_META_ON, KEYCODE_DEL)
- },
- shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_DPAD_LEFT)
},
// Take a full screenshot:
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index 7743c53c6900..ef242678a8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -46,8 +46,11 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi
return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
}
- suspend fun deleteShortcutCurrentlyBeingCustomized():
- ShortcutCustomizationRequestResult {
+ suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
return customShortcutRepository.deleteShortcutCurrentlyBeingCustomized()
}
+
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customShortcutRepository.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 0381eaec5026..61d11f4df5e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -16,14 +16,22 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
+import android.content.Context
+import android.view.KeyEvent.META_META_ON
import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId
import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories
import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.res.R
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -34,6 +42,7 @@ import kotlinx.coroutines.flow.flowOf
class ShortcutHelperCategoriesInteractor
@Inject
constructor(
+ @Application private val context: Context,
@DefaultShortcutCategories defaultCategoriesRepository: ShortcutCategoriesRepository,
@CustomShortcutCategories customCategoriesRepositoryLazy: Lazy<ShortcutCategoriesRepository>,
) {
@@ -83,10 +92,56 @@ constructor(
.groupBy { it.label }
.entries
.map { (commonLabel, groupedShortcuts) ->
- Shortcut(
- label = commonLabel,
- icon = groupedShortcuts.firstOrNull()?.icon,
- commands = groupedShortcuts.flatMap { it.commands },
+ groupedShortcuts[0].copy(
+ commands = groupedShortcuts.flatMap { it.commands }.sortedBy { it.keys.size },
+ contentDescription =
+ toContentDescription(commonLabel, groupedShortcuts.flatMap { it.commands }),
)
}
+
+ private fun toContentDescription(label: String, commands: List<ShortcutCommand>): String {
+ val pressKey = context.getString(R.string.shortcut_helper_add_shortcut_dialog_placeholder)
+ val andConjunction =
+ context.getString(R.string.shortcut_helper_key_combinations_and_conjunction)
+ val orConjunction =
+ context.getString(R.string.shortcut_helper_key_combinations_or_separator)
+ val forwardSlash =
+ context.getString(R.string.shortcut_helper_key_combinations_forward_slash)
+ return buildString {
+ append("$label, $pressKey")
+ commands.forEachIndexed { i, shortcutCommand ->
+ if (i > 0) {
+ append(", $orConjunction")
+ }
+ shortcutCommand.keys.forEachIndexed { j, shortcutKey ->
+ if (j > 0) {
+ append(" $andConjunction")
+ }
+ if (shortcutKey is ShortcutKey.Text) {
+ // Special handling for "/" as TalkBack will not read punctuation by
+ // default.
+ if (shortcutKey.value.equals("/")) {
+ append(" $forwardSlash")
+ } else {
+ append(" ${shortcutKey.value}")
+ }
+ } else if (shortcutKey is ShortcutKey.Icon.ResIdIcon) {
+ val keyLabel =
+ if (shortcutKey.drawableResId == metaModifierIconResId) {
+ ShortcutHelperKeys.modifierLabels[META_META_ON]
+ } else {
+ val keyCode =
+ ShortcutHelperKeys.keyIcons.entries
+ .firstOrNull { it.value == shortcutKey.drawableResId }
+ ?.key
+ ShortcutHelperKeys.specialKeyLabels[keyCode]
+ }
+ if (keyLabel != null) {
+ append(" ${keyLabel.invoke(context)}")
+ }
+ } // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index bf7df7ef8561..55cc8e0905b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -20,18 +20,32 @@ data class Shortcut(
val label: String,
val commands: List<ShortcutCommand>,
val icon: ShortcutIcon? = null,
+ val contentDescription: String = "",
+ val isCustomizable: Boolean = true,
) {
val containsCustomShortcutCommands: Boolean = commands.any { it.isCustom }
}
class ShortcutBuilder(private val label: String) {
val commands = mutableListOf<ShortcutCommand>()
+ var contentDescription = ""
+ var isCustomizable = true
fun command(builder: ShortcutCommandBuilder.() -> Unit) {
commands += ShortcutCommandBuilder().apply(builder).build()
}
- fun build() = Shortcut(label, commands)
+ fun contentDescription(string: () -> String) {
+ contentDescription = string.invoke()
+ }
+
+ fun build() =
+ Shortcut(
+ label,
+ commands,
+ contentDescription = contentDescription,
+ isCustomizable = isCustomizable,
+ )
}
fun shortcut(label: String, block: ShortcutBuilder.() -> Unit): Shortcut =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index 2d3e7f6f6448..095de41237cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -28,4 +28,6 @@ sealed interface ShortcutCustomizationRequestInfo {
val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
val subCategoryLabel: String = "",
) : ShortcutCustomizationRequestInfo
+
+ data object Reset : ShortcutCustomizationRequestInfo
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperExclusions.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperExclusions.kt
new file mode 100644
index 000000000000..20b74c86973f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperExclusions.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyboard.shortcut.shared.model
+
+import android.content.Context
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class ShortcutHelperExclusions @Inject constructor(private val context: Context) {
+ private val nonCustomizableShortcutsLabels: List<String>
+ get() =
+ listOf(
+ context.getString(R.string.group_system_cycle_forward),
+ context.getString(R.string.group_system_cycle_back),
+ )
+
+ fun isShortcutCustomizable(label: String) = !nonCustomizableShortcutsLabels.contains(label)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
index 14016783a478..37433ca4faf3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
@@ -16,7 +16,9 @@
package com.android.systemui.keyboard.shortcut.shared.model
-data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>)
+data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>) {
+ val containsCustomShortcuts: Boolean = shortcuts.any { it.containsCustomShortcutCommands }
+}
class ShortcutSubCategoryBuilder(val label: String) {
private val shortcuts = mutableListOf<Shortcut>()
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 f28618bb8cf4..274fa59045d7 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
@@ -31,6 +31,7 @@ import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutCustomizatio
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -52,14 +53,18 @@ constructor(
override suspend fun onActivated(): Nothing {
viewModel.shortcutCustomizationUiState.collect { uiState ->
- val shouldShowAddDialog = uiState is AddShortcutDialog && !uiState.isDialogShowing
- val shouldShowDeleteDialog = uiState is DeleteShortcutDialog && !uiState.isDialogShowing
- if (shouldShowDeleteDialog || shouldShowAddDialog) {
- dialog = createDialog().also { it.show() }
- viewModel.onDialogShown()
- } else if (uiState is ShortcutCustomizationUiState.Inactive) {
- dialog?.dismiss()
- dialog = null
+ when(uiState){
+ is AddShortcutDialog,
+ is DeleteShortcutDialog,
+ is ResetShortcutDialog -> {
+ if (dialog == null){
+ dialog = createDialog().also { it.show() }
+ }
+ }
+ is ShortcutCustomizationUiState.Inactive -> {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
awaitCancellation()
@@ -83,7 +88,12 @@ constructor(
onKeyPress = { viewModel.onKeyPressed(it) },
onCancel = { dialog.dismiss() },
onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
- onConfirmDeleteShortcut = { coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() } },
+ onConfirmDeleteShortcut = {
+ coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() }
+ },
+ onConfirmResetShortcut = {
+ coroutineScope.launch { viewModel.resetAllCustomShortcuts() }
+ },
)
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
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 20040c673994..ac6708ae983e 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
@@ -68,73 +68,133 @@ fun ShortcutCustomizationDialog(
onCancel: () -> Unit,
onConfirmSetShortcut: () -> Unit,
onConfirmDeleteShortcut: () -> Unit,
+ onConfirmResetShortcut: () -> Unit,
) {
when (uiState) {
is ShortcutCustomizationUiState.AddShortcutDialog -> {
- Column(modifier = modifier) {
- Title(uiState.shortcutLabel)
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_add_shortcut_description
- )
- )
- PromptShortcutModifier(
- modifier =
- Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
- .width(131.dp)
- .height(48.dp),
- defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
- )
- SelectedKeyCombinationContainer(
- shouldShowError = uiState.errorMessage.isNotEmpty(),
- onKeyPress = onKeyPress,
- pressedKeys = uiState.pressedKeys,
- )
- ErrorMessageContainer(uiState.errorMessage)
- DialogButtons(
- onCancel,
- isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
- onConfirm = onConfirmSetShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
- ),
- )
- }
+ AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
}
is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
- Column(modifier) {
- Title(
- title =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
- )
- )
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_description
- )
- )
- DialogButtons(
- onCancel = onCancel,
- onConfirm = onConfirmDeleteShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_remove_button_label
- ),
- )
- }
+ DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
+ }
+ is ShortcutCustomizationUiState.ResetShortcutDialog -> {
+ ResetShortcutDialog(modifier, onCancel, onConfirmResetShortcut)
}
else -> {
- /* No-Op */
+ /* No-op */
}
}
}
@Composable
-fun DialogButtons(
+private fun AddShortcutDialog(
+ modifier: Modifier,
+ uiState: ShortcutCustomizationUiState.AddShortcutDialog,
+ onKeyPress: (KeyEvent) -> Boolean,
+ onCancel: () -> Unit,
+ onConfirmSetShortcut: () -> Unit
+){
+ Column(modifier = modifier) {
+ Title(uiState.shortcutLabel)
+ Description(
+ text =
+ stringResource(
+ id = R.string.shortcut_customize_mode_add_shortcut_description
+ )
+ )
+ PromptShortcutModifier(
+ modifier =
+ Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+ .width(131.dp)
+ .height(48.dp),
+ defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+ )
+ SelectedKeyCombinationContainer(
+ shouldShowError = uiState.errorMessage.isNotEmpty(),
+ onKeyPress = onKeyPress,
+ pressedKeys = uiState.pressedKeys,
+ )
+ ErrorMessageContainer(uiState.errorMessage)
+ DialogButtons(
+ onCancel,
+ isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
+ onConfirm = onConfirmSetShortcut,
+ confirmButtonText =
+ stringResource(
+ R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
+ ),
+ )
+ }
+}
+
+@Composable
+private fun DeleteShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmDeleteShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmDeleteShortcut,
+ )
+}
+
+@Composable
+private fun ResetShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmResetShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmResetShortcut,
+ )
+}
+
+@Composable
+private fun ConfirmationDialog(
+ modifier: Modifier,
+ title: String,
+ description: String,
+ confirmButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Column(modifier) {
+ Title(title = title)
+ Description(text = description)
+ DialogButtons(
+ onCancel = onCancel,
+ onConfirm = onConfirm,
+ confirmButtonText = confirmButtonText,
+ )
+ }
+}
+
+@Composable
+private fun DialogButtons(
onCancel: () -> Unit,
isConfirmButtonEnabled: Boolean = true,
onConfirm: () -> Unit,
@@ -168,7 +228,7 @@ fun DialogButtons(
}
@Composable
-fun ErrorMessageContainer(errorMessage: String) {
+private fun ErrorMessageContainer(errorMessage: String) {
if (errorMessage.isNotEmpty()) {
Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
Text(
@@ -185,7 +245,7 @@ fun ErrorMessageContainer(errorMessage: String) {
}
@Composable
-fun SelectedKeyCombinationContainer(
+private fun SelectedKeyCombinationContainer(
shouldShowError: Boolean,
onKeyPress: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
@@ -352,7 +412,7 @@ private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
}
@Composable
-fun ActionKeyText() {
+private fun ActionKeyText() {
Text(
text = "Action",
style = MaterialTheme.typography.titleMedium,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index e3675de5d197..272491850c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.ui.composable
import android.graphics.drawable.Icon
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -56,6 +57,7 @@ import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.DeleteOutline
import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.CenterAlignedTopAppBar
@@ -96,6 +98,8 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
@@ -111,6 +115,7 @@ import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
@@ -121,7 +126,6 @@ import com.android.systemui.keyboard.shortcut.ui.model.IconSource
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import kotlinx.coroutines.delay
@Composable
@@ -185,6 +189,7 @@ private fun ActiveShortcutHelper(
onKeyboardSettingsClicked,
shortcutsUiState.isShortcutCustomizerFlagEnabled,
onCustomizationRequested,
+ shortcutsUiState.shouldShowResetButton,
)
}
}
@@ -375,6 +380,7 @@ private fun ShortcutHelperTwoPane(
onKeyboardSettingsClicked: () -> Unit,
isShortcutCustomizerFlagEnabled: Boolean,
onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+ shouldShowResetButton: Boolean,
) {
val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
var isCustomizing by remember { mutableStateOf(false) }
@@ -387,11 +393,14 @@ private fun ShortcutHelperTwoPane(
TitleBar(isCustomizing)
}
if (isShortcutCustomizerFlagEnabled) {
- if (isCustomizing) {
- DoneButton(onClick = { isCustomizing = false })
- } else {
- CustomizeButton(onClick = { isCustomizing = true })
- }
+ CustomizationButtonsContainer(
+ isCustomizing = isCustomizing,
+ onToggleCustomizationMode = { isCustomizing = !isCustomizing },
+ onReset = {
+ onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ },
+ shouldShowResetButton = shouldShowResetButton,
+ )
} else {
Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
}
@@ -420,6 +429,38 @@ private fun ShortcutHelperTwoPane(
}
@Composable
+private fun CustomizationButtonsContainer(
+ isCustomizing: Boolean,
+ shouldShowResetButton: Boolean,
+ onToggleCustomizationMode: () -> Unit,
+ onReset: () -> Unit,
+) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ if (isCustomizing) {
+ if (shouldShowResetButton) {
+ ResetButton(onClick = onReset)
+ }
+ DoneButton(onClick = onToggleCustomizationMode)
+ } else {
+ CustomizeButton(onClick = onToggleCustomizationMode)
+ }
+ }
+}
+
+@Composable
+private fun ResetButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ onClick = onClick,
+ color = Color.Transparent,
+ width = 99.dp,
+ iconSource = IconSource(imageVector = Icons.Default.Refresh),
+ text = stringResource(id = R.string.shortcut_helper_reset_button_text),
+ contentColor = MaterialTheme.colorScheme.primary,
+ border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp),
+ )
+}
+
+@Composable
private fun CustomizeButton(onClick: () -> Unit) {
ShortcutHelperButton(
onClick = onClick,
@@ -465,14 +506,13 @@ private fun EndSidePanel(
onCustomizationRequested = { requestInfo ->
when (requestInfo) {
is ShortcutCustomizationRequestInfo.Add ->
- onCustomizationRequested(
- requestInfo.copy(categoryType = category.type)
- )
+ onCustomizationRequested(requestInfo.copy(categoryType = category.type))
is ShortcutCustomizationRequestInfo.Delete ->
- onCustomizationRequested(
- requestInfo.copy(categoryType = category.type)
- )
+ onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
@@ -525,7 +565,7 @@ private fun SubCategoryContainerDualPane(
modifier = Modifier.padding(vertical = 8.dp),
searchQuery = searchQuery,
shortcut = shortcut,
- isCustomizing = isCustomizing,
+ isCustomizing = isCustomizing && shortcut.isCustomizable,
onCustomizationRequested = { requestInfo ->
when (requestInfo) {
is ShortcutCustomizationRequestInfo.Add ->
@@ -537,6 +577,9 @@ private fun SubCategoryContainerDualPane(
onCustomizationRequested(
requestInfo.copy(subCategoryLabel = subCategory.label)
)
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
@@ -572,20 +615,31 @@ private fun Shortcut(
}
.focusable(interactionSource = interactionSource)
.padding(8.dp)
+ .semantics { contentDescription = shortcut.contentDescription }
) {
Row(
- modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f),
+ modifier =
+ Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics {
+ hideFromAccessibility()
+ },
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (shortcut.icon != null) {
- ShortcutIcon(shortcut.icon, modifier = Modifier.size(24.dp))
+ ShortcutIcon(
+ shortcut.icon,
+ modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() },
+ )
}
- ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
+ ShortcutDescriptionText(
+ searchQuery = searchQuery,
+ shortcut = shortcut,
+ modifier = Modifier.semantics { hideFromAccessibility() },
+ )
}
- Spacer(modifier = Modifier.width(24.dp))
+ Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() })
ShortcutKeyCombinations(
- modifier = Modifier.weight(.666f),
+ modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() },
shortcut = shortcut,
isCustomizing = isCustomizing,
onAddShortcutRequested = {
@@ -747,7 +801,10 @@ private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() ->
private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) {
Text(
text = key.value,
- modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp),
+ modifier =
+ Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics {
+ hideFromAccessibility()
+ },
style = MaterialTheme.typography.titleSmall,
)
}
@@ -771,7 +828,7 @@ private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) {
Spacer(Modifier.width(spacing))
Text(
text = stringResource(R.string.shortcut_helper_key_combinations_or_separator),
- modifier = Modifier.align(Alignment.CenterVertically),
+ modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() },
style = MaterialTheme.typography.titleSmall,
)
Spacer(Modifier.width(spacing))
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 58ce194694df..55c0fe297bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -228,74 +228,8 @@ fun ShortcutHelperButton(
contentColor: Color,
contentPaddingHorizontal: Dp = 16.dp,
contentPaddingVertical: Dp = 10.dp,
-) {
- ClickableShortcutSurface(
- onClick = onClick,
- shape = shape,
- color = color,
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlineStrokeWidth = 3.dp,
- focusOutlinePadding = 2.dp,
- surfaceCornerRadius = 28.dp,
- focusOutlineCornerRadius = 33.dp,
- ),
- ) {
- Row(
- modifier =
- Modifier.padding(
- horizontal = contentPaddingHorizontal,
- vertical = contentPaddingVertical,
- ),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center,
- ) {
- if (iconSource.imageVector != null) {
- Icon(
- tint = contentColor,
- imageVector = iconSource.imageVector,
- contentDescription = null,
- modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
- )
- }
-
- if (iconSource.imageVector != null && text != null) {
- Spacer(modifier = Modifier.weight(1f))
- }
-
- if (text != null) {
- Text(
- text,
- color = contentColor,
- fontSize = 14.sp,
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.wrapContentSize(Alignment.Center),
- )
- }
- }
- }
-}
-
-@Composable
-fun ShortcutHelperButton(
- modifier: Modifier = Modifier,
- onClick: () -> Unit,
- shape: Shape = RoundedCornerShape(360.dp),
- color: Color,
- width: Dp,
- height: Dp = 40.dp,
- iconSource: IconSource = IconSource(),
- text: String? = null,
- contentColor: Color,
- contentPaddingHorizontal: Dp = 16.dp,
- contentPaddingVertical: Dp = 10.dp,
enabled: Boolean = true,
+ border: BorderStroke? = null,
) {
ShortcutHelperButtonSurface(
onClick = onClick,
@@ -305,6 +239,7 @@ fun ShortcutHelperButton(
enabled = enabled,
width = width,
height = height,
+ border = border,
) {
Row(
modifier =
@@ -342,7 +277,7 @@ fun ShortcutHelperButton(
}
@Composable
-fun ShortcutHelperButtonSurface(
+private fun ShortcutHelperButtonSurface(
onClick: () -> Unit,
shape: Shape,
color: Color,
@@ -350,6 +285,7 @@ fun ShortcutHelperButtonSurface(
enabled: Boolean,
width: Dp,
height: Dp,
+ border: BorderStroke?,
content: @Composable () -> Unit,
) {
if (enabled) {
@@ -357,6 +293,7 @@ fun ShortcutHelperButtonSurface(
onClick = onClick,
shape = shape,
color = color,
+ border = border,
modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
interactionsConfig =
InteractionsConfig(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
index f5d478b5855d..27287721b333 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
@@ -31,4 +31,6 @@ data class ShortcutCategoryUi(
iconSource: IconSource,
shortcutCategory: ShortcutCategory,
) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories)
+
+ val containsCustomShortcuts: Boolean = subCategories.any { it.containsCustomShortcuts }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 990257d642ff..36c5ae0717be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -23,11 +23,12 @@ sealed interface ShortcutCustomizationUiState {
val shortcutLabel: String,
val errorMessage: String = "",
val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
- val isDialogShowing: Boolean,
val pressedKeys: List<ShortcutKey> = emptyList(),
) : ShortcutCustomizationUiState
- data class DeleteShortcutDialog(val isDialogShowing: Boolean) : ShortcutCustomizationUiState
+ data object DeleteShortcutDialog : ShortcutCustomizationUiState
+
+ data object ResetShortcutDialog : ShortcutCustomizationUiState
data object Inactive : ShortcutCustomizationUiState
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index 02b0b43ea979..52ab157a0e70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -25,6 +25,7 @@ sealed interface ShortcutsUiState {
val shortcutCategories: List<ShortcutCategoryUi>,
val defaultSelectedCategory: ShortcutCategoryType?,
val isShortcutCustomizerFlagEnabled: Boolean = false,
+ val shouldShowResetButton: Boolean = false,
) : ShortcutsUiState
data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index b467bb481443..92e25929fe4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -72,24 +73,19 @@ constructor(
shortcutLabel = requestInfo.label,
defaultCustomShortcutModifierKey =
shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
- isDialogShowing = false,
pressedKeys = emptyList(),
)
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
is ShortcutCustomizationRequestInfo.Delete -> {
- _shortcutCustomizationUiState.value = DeleteShortcutDialog(isDialogShowing = false)
+ _shortcutCustomizationUiState.value = DeleteShortcutDialog
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
- }
- }
- fun onDialogShown() {
- _shortcutCustomizationUiState.update { uiState ->
- (uiState as? AddShortcutDialog)?.copy(isDialogShowing = true)
- ?: (uiState as? DeleteShortcutDialog)?.copy(isDialogShowing = true)
- ?: uiState
+ ShortcutCustomizationRequestInfo.Reset -> {
+ _shortcutCustomizationUiState.value = ResetShortcutDialog
+ }
}
}
@@ -134,8 +130,18 @@ constructor(
}
suspend fun deleteShortcutCurrentlyBeingCustomized() {
- val result =
- shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+ val result = shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+
+ _shortcutCustomizationUiState.update { uiState ->
+ when (result) {
+ ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+ else -> uiState
+ }
+ }
+ }
+
+ suspend fun resetAllCustomShortcuts() {
+ val result = shortcutCustomizationInteractor.resetAllCustomShortcuts()
_shortcutCustomizationUiState.update { uiState ->
when (result) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 08fd0af81006..0f5f07393114 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
import android.app.role.RoleManager
+import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.util.Log
import androidx.compose.material.icons.Icons
@@ -40,7 +41,6 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,10 +51,12 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
+import javax.inject.Inject
class ShortcutHelperViewModel
@Inject
constructor(
+ private val context: Context,
private val roleManager: RoleManager,
private val userTracker: UserTracker,
@Background private val backgroundScope: CoroutineScope,
@@ -88,6 +90,7 @@ constructor(
shortcutCategories = shortcutCategoriesUi,
defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(),
+ shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi)
)
}
}
@@ -97,6 +100,10 @@ constructor(
initialValue = ShortcutsUiState.Inactive,
)
+ private fun shouldShowResetButton(categoriesUi: List<ShortcutCategoryUi>): Boolean {
+ return categoriesUi.any { it.containsCustomShortcuts }
+ }
+
private fun convertCategoriesModelToUiModel(
categories: List<ShortcutCategory>
): List<ShortcutCategoryUi> {
@@ -136,13 +143,13 @@ constructor(
private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String =
when (type) {
ShortcutCategoryType.System ->
- userContext.getString(R.string.shortcut_helper_category_system)
+ context.getString(R.string.shortcut_helper_category_system)
ShortcutCategoryType.MultiTasking ->
- userContext.getString(R.string.shortcut_helper_category_multitasking)
+ context.getString(R.string.shortcut_helper_category_multitasking)
ShortcutCategoryType.InputMethodEditor ->
- userContext.getString(R.string.shortcut_helper_category_input)
+ context.getString(R.string.shortcut_helper_category_input)
ShortcutCategoryType.AppCategories ->
- userContext.getString(R.string.shortcut_helper_category_app_shortcuts)
+ context.getString(R.string.shortcut_helper_category_app_shortcuts)
is CurrentApp -> getApplicationLabelForCurrentApp(type)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index a94df091230d..7a72732ea6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,6 +36,7 @@ import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCall
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -44,6 +45,7 @@ class CustomizationProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+ @Inject lateinit var shadeModeInteractor: ShadeModeInteractor
@Inject lateinit var previewManager: KeyguardRemotePreviewManager
@Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
@@ -73,6 +75,11 @@ class CustomizationProvider :
MATCH_CODE_ALL_SELECTIONS,
)
addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS)
+ addURI(
+ Contract.AUTHORITY,
+ Contract.RuntimeValuesTable.TABLE_NAME,
+ MATCH_CODE_ALL_RUNTIME_VALUES,
+ )
}
override fun onCreate(): Boolean {
@@ -94,7 +101,8 @@ class CustomizationProvider :
MATCH_CODE_ALL_SLOTS,
MATCH_CODE_ALL_AFFORDANCES,
MATCH_CODE_ALL_FLAGS,
- MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
+ MATCH_CODE_ALL_SELECTIONS,
+ MATCH_CODE_ALL_RUNTIME_VALUES -> "vnd.android.cursor.dir/vnd."
else -> null
}
@@ -113,6 +121,7 @@ class CustomizationProvider :
Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
)
MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
+ MATCH_CODE_ALL_RUNTIME_VALUES -> Contract.RuntimeValuesTable.TABLE_NAME
else -> null
}
@@ -146,6 +155,7 @@ class CustomizationProvider :
MATCH_CODE_ALL_SLOTS -> querySlots()
MATCH_CODE_ALL_SELECTIONS -> querySelections()
MATCH_CODE_ALL_FLAGS -> queryFlags()
+ MATCH_CODE_ALL_RUNTIME_VALUES -> queryRuntimeValues()
else -> null
}
}
@@ -334,6 +344,23 @@ class CustomizationProvider :
}
}
+ private fun queryRuntimeValues(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.RuntimeValuesTable.Columns.NAME,
+ Contract.RuntimeValuesTable.Columns.VALUE,
+ )
+ )
+ .apply {
+ addRow(
+ arrayOf(
+ Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE,
+ if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0,
+ )
+ )
+ }
+ }
+
private suspend fun deleteSelection(uri: Uri, selectionArgs: Array<out String>?): Int {
if (selectionArgs == null) {
throw IllegalArgumentException(
@@ -370,5 +397,6 @@ class CustomizationProvider :
private const val MATCH_CODE_ALL_AFFORDANCES = 2
private const val MATCH_CODE_ALL_SELECTIONS = 3
private const val MATCH_CODE_ALL_FLAGS = 4
+ private const val MATCH_CODE_ALL_RUNTIME_VALUES = 5
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 0101e099084e..096439b1008d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -62,6 +62,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
+import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -248,4 +249,10 @@ public interface KeyguardModule {
@IntoMap
@ClassKey(KeyguardUpdateMonitor.class)
CoreStartable bindsKeyguardUpdateMonitor(KeyguardUpdateMonitor keyguardUpdateMonitor);
+
+ /***/
+ @Binds
+ @IntoMap
+ @ClassKey(AlternateBouncerWindowViewBinder.class)
+ CoreStartable bindsAlternateBouncerWindowViewBinder(AlternateBouncerWindowViewBinder binder);
}
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 d1f9fa259c6b..e8d3bfac6361 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
@@ -27,7 +27,6 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.ZenModeConfig
import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -99,15 +98,6 @@ constructor(
private var oldIsAvailable = false
private var settingsValue: Int = 0
- private val dndMode: StateFlow<ZenMode?> by lazy {
- ModesUi.assertInNewMode()
- interactor.dndMode.stateIn(
- scope = backgroundScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- }
-
private val isAvailable: StateFlow<Boolean> by lazy {
ModesUi.assertInNewMode()
interactor.isZenAvailable.stateIn(
@@ -146,7 +136,7 @@ constructor(
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
if (ModesUi.isEnabled) {
- combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ combine(isAvailable, interactor.dndMode) { isAvailable, dndMode ->
if (!isAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
} else if (dndMode?.isActive == true) {
@@ -222,7 +212,7 @@ constructor(
if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
} else {
- val dnd = dndMode.value
+ val dnd = interactor.dndMode.value
if (dnd == null) {
Log.wtf(TAG, "Triggered DND but it's null!?")
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
index 8906156252a6..d335a1806a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
@@ -17,13 +17,15 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.Intent
+import android.provider.Settings
import android.util.Log
-import com.android.systemui.Flags.glanceableHubShortcutButton
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.communal.data.repository.CommunalSceneRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
@@ -44,6 +46,7 @@ constructor(
@Application private val context: Context,
private val communalSceneRepository: CommunalSceneRepository,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val sceneInteractor: SceneInteractor,
) : KeyguardQuickAffordanceConfig {
@@ -59,8 +62,7 @@ constructor(
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
get() = flow {
emit(
- // TODO(b/378113263): Gate on getV2FlagEnabled() when ready.
- if (!glanceableHubShortcutButton()) {
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
Log.i(TAG, "Button hidden on lockscreen: flag not enabled.")
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
} else if (!communalInteractor.isCommunalEnabled.value) {
@@ -79,14 +81,19 @@ constructor(
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
- // TODO(b/378113263): Gate on getV2FlagEnabled() when ready.
- return if (!glanceableHubShortcutButton()) {
+ return if (!communalSettingsInteractor.isV2FlagEnabled()) {
Log.i(TAG, "Button unavailable in picker: flag not enabled.")
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
} else if (!communalInteractor.isCommunalEnabled.value) {
Log.i(TAG, "Button disabled in picker: hub not enabled in settings.")
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- context.getString(R.string.glanceable_hub_lockscreen_affordance_disabled_text)
+ explanation =
+ context.getString(R.string.glanceable_hub_lockscreen_affordance_disabled_text),
+ actionText =
+ context.getString(
+ R.string.glanceable_hub_lockscreen_affordance_action_button_label
+ ),
+ actionIntent = Intent(Settings.ACTION_LOCKSCREEN_SETTINGS),
)
} else {
KeyguardQuickAffordanceConfig.PickerScreenState.Default()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index deef2a6c3a4c..a9992112f893 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -21,7 +21,6 @@ import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -49,7 +48,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.filter
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -178,7 +176,8 @@ constructor(
newScene = CommunalScenes.Communal,
loggingReason = "FromDreamingTransitionInteractor",
transitionKey =
- if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+ if (communalSettingsInteractor.isV2FlagEnabled())
+ CommunalTransitionKeys.SimpleFade
else null,
)
} else {
@@ -226,8 +225,15 @@ constructor(
scope.launch {
keyguardInteractor.isAbleToDream
- .filter { !it }
- .sample(deviceEntryInteractor.isUnlocked, ::Pair)
+ .filterRelevantKeyguardStateAnd { !it }
+ .sample(
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.isUnlocked
+ } else {
+ keyguardInteractor.isKeyguardDismissible
+ },
+ ::Pair,
+ )
.collect { (_, dismissable) ->
// TODO(b/349837588): Add check for -> OCCLUDED.
if (dismissable) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 21090c12e9fe..cc8652c76181 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.logging.KeyguardLogger
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
@@ -28,15 +27,12 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
-import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -66,8 +62,6 @@ constructor(
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
- powerInteractor: PowerInteractor,
- alternateBouncerInteractor: AlternateBouncerInteractor,
shadeInteractor: Lazy<ShadeInteractor>,
keyguardInteractor: Lazy<KeyguardInteractor>,
sceneInteractor: Lazy<SceneInteractor>,
@@ -144,42 +138,6 @@ constructor(
}
}
- /** Flow that emits whenever we need to reset the dismiss action */
- private val resetDismissAction: Flow<Unit> =
- combine(
- if (SceneContainerFlag.isEnabled) {
- // Using currentScene instead of isFinishedIn because of a race condition that
- // forms between isFinishedIn(Gone) and isOnShadeWhileUnlocked where the latter
- // emits false before the former emits true, causing the evaluation of the
- // combine to come up with true, temporarily, before settling on false, which is
- // a valid final state. That causes an incorrect reset of the dismiss action to
- // occur before it gets executed.
- sceneInteractor
- .get()
- .currentScene
- .map { it == Scenes.Gone }
- .distinctUntilChanged()
- } else {
- transitionInteractor.isFinishedIn(
- scene = Scenes.Gone,
- stateWithoutSceneContainer = GONE,
- )
- },
- transitionInteractor.isFinishedIn(
- scene = Scenes.Bouncer,
- stateWithoutSceneContainer = PRIMARY_BOUNCER,
- ),
- alternateBouncerInteractor.isVisible,
- isOnShadeWhileUnlocked,
- powerInteractor.isAsleep,
- ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked, isAsleep ->
- (!isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked) ||
- isAsleep
- }
- .filter { it }
- .sampleFilter(dismissAction) { it !is DismissAction.None }
- .map {}
-
fun runDismissAnimationOnKeyguard(): Boolean {
return willAnimateDismissActionOnLockscreen.value
}
@@ -220,19 +178,15 @@ constructor(
}
}
- launch {
- resetDismissAction.collect {
- log("resetDismissAction")
- repository.dismissAction.value.onCancelAction.run()
- clearDismissAction()
- }
- }
-
launch { repository.dismissAction.collect { log("updatedDismissAction=$it") } }
awaitCancellation()
}
}
+ fun clearDismissAction() {
+ repository.setDismissAction(DismissAction.None)
+ }
+
/** Run the dismiss action and starts the dismiss keyguard transition. */
private suspend fun runDismissAction() {
val dismissAction = repository.dismissAction.value
@@ -249,10 +203,6 @@ constructor(
}
}
- private fun clearDismissAction() {
- repository.setDismissAction(DismissAction.None)
- }
-
private fun log(message: String) {
keyguardLogger.log(TAG, LogLevel.DEBUG, message)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 2d7da38535b1..0a4022ad4de8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -39,7 +40,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
/** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
@@ -53,8 +53,8 @@ constructor(
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
trustRepository: TrustRepository,
- alternateBouncerInteractor: AlternateBouncerInteractor,
powerInteractor: PowerInteractor,
) {
/*
@@ -151,13 +151,16 @@ constructor(
dismissCallbackRegistry.addCallback(callback)
}
- // This will either show the bouncer, or dismiss the keyguard if insecure.
- // We currently need to request showing the primary bouncer in order to start a
- // transition to PRIMARY_BOUNCER. Once we refactor that so that starting the
- // transition is what causes the bouncer to show, we can remove this entire method,
- // and simply ask KeyguardTransitionInteractor to transition to a bouncer state or
- // dismiss keyguard.
- primaryBouncerInteractor.show(true)
+ // This will either show the bouncer, or dismiss the keyguard if insecure. We
+ // currently need to request showing the bouncer in order to start a transition to
+ // *_BOUNCER. Once we refactor that so that starting the transition is what causes
+ // the bouncer to show, we can remove this entire method, and simply call
+ // KeyguardDismissTransitionInteractor#startDismissKeyguardTransition.
+ if (alternateBouncerInteractor.canShowAlternateBouncer.value) {
+ alternateBouncerInteractor.forceShow()
+ } else {
+ primaryBouncerInteractor.show(true)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 7cd2744cb7dc..f078fe26902e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -256,6 +256,16 @@ constructor(
val isTransitioningBetweenDesiredScenes =
sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
+ // When in STL A -> B settles in A we can't do the same in KTF as KTF requires us to
+ // start B -> A to get back to A. [LockscreenSceneTransitionInteractor] will emit these
+ // steps but because STL is Idle(A) at this point (and never even started a B -> A in
+ // the first place) [isTransitioningBetweenDesiredScenes] will not be satisfied. We need
+ // this condition to not filter out the STARTED and FINISHED step of the "artificially"
+ // reversed B -> A transition.
+ val belongsToInstantReversedTransition =
+ sceneInteractor.transitionState.value.isIdle(toScene) &&
+ sceneTransitionPair.value.previousValue.isTransitioning(toScene, fromScene)
+
// We can't compare the terminal step with the current sceneTransition because
// a) STL has no guarantee that it will settle in Idle() when finished/canceled
// b) Comparing to Idle(toScene) would make any other FINISHED step settling in
@@ -267,7 +277,8 @@ constructor(
return@filter isTransitioningBetweenLockscreenStates ||
isTransitioningBetweenDesiredScenes ||
- terminalStepBelongsToPreviousTransition
+ terminalStepBelongsToPreviousTransition ||
+ belongsToInstantReversedTransition
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 9df293bf2c15..3b99bb4c40fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -276,7 +276,11 @@ constructor(
false
} else if (
currentState == KeyguardState.DREAMING &&
- deviceEntryInteractor.get().isUnlocked.value
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.get().isUnlocked.value
+ } else {
+ keyguardInteractor.isKeyguardDismissible.value
+ }
) {
// Dreams dismiss keyguard and return to GONE if they can.
false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 7a368999ecc9..33783b515763 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -16,9 +16,7 @@
package com.android.systemui.keyguard.ui.binder
-import android.graphics.PixelFormat
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -36,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewLayoutParams
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
@@ -68,28 +67,6 @@ constructor(
private val windowManager: Lazy<WindowManager>,
private val layoutInflater: Lazy<LayoutInflater>,
) : CoreStartable {
- private val layoutParams: WindowManager.LayoutParams
- get() =
- WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
- WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
- PixelFormat.TRANSLUCENT,
- )
- .apply {
- title = "AlternateBouncerView"
- fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
- gravity = Gravity.TOP or Gravity.LEFT
- layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- privateFlags =
- WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
- WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
- // Avoid announcing window title.
- accessibilityTitle = " "
- }
private var alternateBouncerView: ConstraintLayout? = null
@@ -176,7 +153,9 @@ constructor(
as ConstraintLayout
Log.d(TAG, "Adding alternate bouncer view")
- windowManager.get().addView(alternateBouncerView, layoutParams)
+ windowManager
+ .get()
+ .addView(alternateBouncerView, AlternateBouncerWindowViewLayoutParams.layoutParams)
alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 69856151e41c..3d6cf2f9d1c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -187,10 +187,6 @@ object DeviceEntryIconViewBinder {
launch("$TAG#fpIconView.viewModel") {
fgViewModel.viewModel.collect { viewModel ->
Log.d(TAG, "Updating device entry icon image state $viewModel")
- fgIconView.setImageState(
- view.getIconState(viewModel.type, viewModel.useAodVariant),
- /* merge */ false,
- )
if (viewModel.type.contentDescriptionResId != -1) {
fgIconView.contentDescription =
fgIconView.resources.getString(
@@ -205,6 +201,14 @@ object DeviceEntryIconViewBinder {
viewModel.padding,
viewModel.padding,
)
+ // Set image state at the end after updating other view state. This
+ // method forces the ImageView to recompute the bounds of the drawable.
+ fgIconView.setImageState(
+ view.getIconState(viewModel.type, viewModel.useAodVariant),
+ /* merge */ false,
+ )
+ // Invalidate, just in case the padding changes just after icon changes
+ fgIconView.invalidate()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index b30e1e9b1103..273d763a8c57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -48,8 +48,6 @@ import kotlinx.coroutines.flow.map
object KeyguardClockViewBinder {
private val TAG = KeyguardClockViewBinder::class.simpleName!!
- // When changing to new clock, we need to remove old clock views from burnInLayer
- private var lastClock: ClockController? = null
@JvmStatic
fun bind(
@@ -72,19 +70,33 @@ object KeyguardClockViewBinder {
disposables +=
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ // When changing to new clock, we need to remove old views from burnInLayer
+ var lastClock: ClockController? = null
launch {
- if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.currentClock.collect { currentClock ->
- cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
- addClockViews(currentClock, keyguardRootView)
- updateBurnInLayer(
- keyguardRootView,
- viewModel,
- viewModel.clockSize.value,
- )
- applyConstraints(clockSection, keyguardRootView, true)
+ if (!MigrateClocksToBlueprint.isEnabled) return@launch
+ viewModel.currentClock.collect { currentClock ->
+ if (lastClock != currentClock) {
+ cleanupClockViews(
+ lastClock,
+ keyguardRootView,
+ viewModel.burnInLayer,
+ )
+ lastClock = currentClock
+ }
+
+ addClockViews(currentClock, keyguardRootView)
+ updateBurnInLayer(
+ keyguardRootView,
+ viewModel,
+ viewModel.clockSize.value,
+ )
+ applyConstraints(clockSection, keyguardRootView, true)
+ }
+ }
+ .invokeOnCompletion {
+ cleanupClockViews(lastClock, keyguardRootView, viewModel.burnInLayer)
+ lastClock = null
}
- }
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
@@ -158,6 +170,18 @@ object KeyguardClockViewBinder {
}
}
+ disposables +=
+ keyguardRootView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.currentClock.collect { currentClock ->
+ currentClock?.apply {
+ smallClock.run { events.onThemeChanged(theme) }
+ largeClock.run { events.onThemeChanged(theme) }
+ }
+ }
+ }
+ }
+
return disposables
}
@@ -185,23 +209,18 @@ object KeyguardClockViewBinder {
viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
}
- private fun cleanupClockViews(
- currentClock: ClockController?,
+ fun cleanupClockViews(
+ lastClock: ClockController?,
rootView: ConstraintLayout,
burnInLayer: Layer?,
) {
- if (lastClock == currentClock) {
- return
- }
-
- lastClock?.let { clock ->
- clock.smallClock.layout.views.forEach {
+ lastClock?.run {
+ smallClock.layout.views.forEach {
burnInLayer?.removeView(it)
rootView.removeView(it)
}
- clock.largeClock.layout.views.forEach { rootView.removeView(it) }
+ largeClock.layout.views.forEach { rootView.removeView(it) }
}
- lastClock = currentClock
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index ac302dd26365..c0b3d8345249 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -123,6 +123,7 @@ object KeyguardPreviewClockViewBinder {
)
}
}
+ lastClock = 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 e89be5d6ae4c..741b149f29da 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
@@ -39,7 +39,7 @@ object KeyguardPreviewSmartspaceViewBinder {
smartspace.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch("$TAG#viewModel.selectedClockSize") {
- viewModel.selectedClockSize.collect {
+ viewModel.previewingClockSize.collect {
val topPadding =
when (it) {
ClockSizeSetting.DYNAMIC ->
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 9924a3bcdd6a..eab752877520 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
@@ -66,6 +66,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -187,7 +188,8 @@ constructor(
private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
private val coroutineScope: CoroutineScope
- private var themeStyle: Style? = null
+
+ @Style.Type private var themeStyle: Int? = null
init {
coroutineScope =
@@ -312,6 +314,10 @@ constructor(
)
}
+ fun onClockSizeSelected(clockSize: ClockSizeSetting) {
+ smartspaceViewModel.setOverrideClockSize(clockSize)
+ }
+
fun destroy() {
isDestroyed = true
lockscreenSmartspaceController.disconnect()
@@ -655,6 +661,7 @@ constructor(
// Seed color null means users do not override any color on the clock. The default
// color will need to use wallpaper's extracted color and consider if the
// wallpaper's color is dark or light.
+ @Style.Type
val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
val wallpaperColorScheme = ColorScheme(colors, false, style)
val lightClockColor = wallpaperColorScheme.accent1.s100
@@ -673,6 +680,7 @@ constructor(
}
// In clock preview, we should have a seed color for clock
// before setting clock to clockEventController to avoid updateColor with seedColor == null
+ // So in update colors, it should already have the correct theme in clockFaceController
if (MigrateClocksToBlueprint.isEnabled) {
clockController.clock = clock
}
@@ -707,7 +715,8 @@ constructor(
}
}
- private suspend fun fetchThemeStyleFromSetting(): Style {
+ @Style.Type
+ private suspend fun fetchThemeStyleFromSetting(): Int {
val overlayPackageJson =
withContext(backgroundDispatcher) {
secureSettings.getString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index f228e26f92f7..d51708ffc5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -25,18 +25,24 @@ import android.os.Messenger
import android.util.ArrayMap
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking
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.shared.model.ClockSizeSetting
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_DYNAMIC
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_SMALL
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_CLOCK_SIZE
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_CLOCK_SIZE
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
@@ -44,7 +50,6 @@ import com.android.systemui.util.kotlin.logD
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class KeyguardRemotePreviewManager
@@ -86,17 +91,8 @@ constructor(
renderer.render()
renderer.hostToken?.linkToDeath(observer, 0)
val result = Bundle()
- result.putParcelable(
- KEY_PREVIEW_SURFACE_PACKAGE,
- renderer.surfacePackage,
- )
- val messenger =
- Messenger(
- Handler(
- backgroundHandler.looper,
- observer,
- )
- )
+ result.putParcelable(KEY_PREVIEW_SURFACE_PACKAGE, renderer.surfacePackage)
+ val messenger = Messenger(Handler(backgroundHandler.looper, observer))
// NOTE: The process on the other side can retain messenger indefinitely.
// (e.g. GC might not trigger and cleanup the reference)
val msg = Message.obtain()
@@ -191,6 +187,18 @@ class PreviewLifecycleObserver(
MESSAGE_ID_HIDE_SMART_SPACE -> {
checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE))
}
+ MESSAGE_ID_PREVIEW_CLOCK_SIZE -> {
+ message.data
+ .getString(KEY_CLOCK_SIZE)
+ ?.let {
+ when (it) {
+ CLOCK_SIZE_DYNAMIC -> ClockSizeSetting.DYNAMIC
+ CLOCK_SIZE_SMALL -> ClockSizeSetting.SMALL
+ else -> null
+ }
+ }
+ ?.let { checkNotNull(renderer).onClockSizeSelected(it) }
+ }
else -> checkNotNull(onDestroy).invoke(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt
new file mode 100644
index 000000000000..d3bc79eb89e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewBinder.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.keyguard.ui.view
+
+import android.content.Context
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.compose.ui.platform.ComposeView
+import com.android.systemui.CoreStartable
+import com.android.systemui.compose.ComposeInitializer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.composable.AlternateBouncer
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.repeatWhenAttachedToWindow
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+/** Drives the showing and hiding of the alternate bouncer window. */
+@SysUISingleton
+class AlternateBouncerWindowViewBinder
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Application private val context: Context,
+ private val viewModel: AlternateBouncerViewModel,
+ private val dependencies: AlternateBouncerDependencies,
+ private val windowManager: WindowManager,
+) : CoreStartable {
+
+ override fun start() {
+ if (!SceneContainerFlag.isEnabled) {
+ return
+ }
+
+ applicationScope.launch {
+ viewModel.isVisible
+ .distinctUntilChanged()
+ .filter { it }
+ .collect {
+ windowManager.addView(
+ createView(),
+ AlternateBouncerWindowViewLayoutParams.layoutParams,
+ )
+ }
+ }
+ }
+
+ private fun createView(): View {
+ val root = FrameLayout(context)
+ val composeView =
+ ComposeView(context).apply {
+ setContent {
+ AlternateBouncer(
+ alternateBouncerDependencies = dependencies,
+ onHideAnimationFinished = {
+ if (root.isAttachedToWindow) {
+ windowManager.removeView(root)
+ }
+ },
+ )
+ }
+ }
+
+ root.repeatWhenAttached {
+ root.repeatWhenAttachedToWindow {
+ try {
+ ComposeInitializer.onAttachedToWindow(root)
+ root.addView(composeView)
+ awaitCancellation()
+ } finally {
+ root.removeView(composeView)
+ ComposeInitializer.onDetachedFromWindow(root)
+ }
+ }
+ }
+
+ return root
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt
new file mode 100644
index 000000000000..5585c21aa217
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/AlternateBouncerWindowViewLayoutParams.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.view.Gravity
+import android.view.WindowManager
+
+object AlternateBouncerWindowViewLayoutParams {
+ val layoutParams: WindowManager.LayoutParams
+ get() =
+ WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT,
+ )
+ .apply {
+ title = "AlternateBouncerView"
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ // Avoid announcing window title.
+ accessibilityTitle = " "
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index f0bccacadc57..85ce5cd0f9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -23,7 +23,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -36,9 +38,7 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class AlternateBouncerToPrimaryBouncerTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -46,9 +46,23 @@ constructor(
edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer),
)
.setupWithoutSceneContainer(
- edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER),
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER)
)
+ private val alphaForAnimationStep: (Float) -> Float =
+ when {
+ SceneContainerFlag.isEnabled -> { step ->
+ 1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
+ }
+ else -> { step -> 1f - step }
+ }
+
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ onStep = alphaForAnimationStep,
+ )
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 1c4498212502..0280d17f4ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -25,7 +25,7 @@ import com.android.systemui.plugins.clocks.DefaultClockFaceLayout.Companion.getS
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -38,21 +38,30 @@ constructor(
val clockViewModel: KeyguardClockViewModel,
private val systemBarUtils: SystemBarUtilsProxy,
) {
-
- val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
+ // overrideClockSize will override the clock size that is currently set to the system.
+ private val overrideClockSize: MutableStateFlow<ClockSizeSetting?> = MutableStateFlow(null)
+ val previewingClockSize =
+ combine(overrideClockSize, interactor.selectedClockSize) {
+ overrideClockSize,
+ selectedClockSize ->
+ overrideClockSize ?: selectedClockSize
+ }
val shouldHideSmartspace: Flow<Boolean> =
- combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
- (size, currentClockId) ->
+ combine(previewingClockSize, interactor.currentClockId, ::Pair).map { (size, clockId) ->
when (size) {
// TODO (b/284122375) This is temporary. We should use clockController
// .largeClock.config.hasCustomWeatherDataDisplay instead, but
// ClockRegistry.createCurrentClock is not reliable.
- ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+ ClockSizeSetting.DYNAMIC -> clockId == "DIGITAL_CLOCK_WEATHER"
ClockSizeSetting.SMALL -> false
}
}
+ fun setOverrideClockSize(clockSize: ClockSizeSetting) {
+ overrideClockSize.value = clockSize
+ }
+
fun getSmartspaceStartPadding(context: Context): Int {
return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 341b8d87eeef..1b39d55d1f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -30,14 +30,10 @@ import com.android.systemui.media.controls.util.MediaSmartspaceLogger
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_SEEN_EVENT
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.time.SystemClock
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.util.Locale
import java.util.TreeMap
import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -49,37 +45,9 @@ class MediaFilterRepository
constructor(
@Application private val applicationContext: Context,
private val systemClock: SystemClock,
- private val configurationController: ConfigurationController,
private val smartspaceLogger: MediaSmartspaceLogger,
) {
- val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- trySend(Unit)
- }
-
- override fun onThemeChanged() {
- trySend(Unit)
- }
-
- override fun onUiModeChanged() {
- trySend(Unit)
- }
-
- override fun onLocaleListChanged() {
- if (locale != applicationContext.resources.configuration.locales.get(0)) {
- locale = applicationContext.resources.configuration.locales.get(0)
- trySend(Unit)
- }
- }
- }
- configurationController.addCallback(callback)
- trySend(Unit)
- awaitClose { configurationController.removeCallback(callback) }
- }
-
/** Instance id of media control that recommendations card reactivated. */
private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
val reactivatedId: StateFlow<InstanceId?> = _reactivatedId.asStateFlow()
@@ -190,7 +158,7 @@ constructor(
fun addMediaDataLoadingState(
mediaDataLoadingModel: MediaDataLoadingModel,
- isUpdate: Boolean = true
+ isUpdate: Boolean = true,
) {
val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
sortedMap.putAll(
@@ -395,7 +363,7 @@ constructor(
logSmarspaceRecommendationCardUserEvent(
SMARTSPACE_CARD_SEEN_EVENT,
surface,
- visibleIndex
+ visibleIndex,
)
}
}
@@ -409,7 +377,7 @@ constructor(
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
instanceId: InstanceId? = null,
- isRec: Boolean = false
+ isRec: Boolean = false,
) {
_currentMedia.value.forEachIndexed { index, mediaCommonModel ->
when (mediaCommonModel) {
@@ -423,7 +391,7 @@ constructor(
surface,
mediaCommonModel.mediaLoadedModel.isSsReactivated,
interactedSubCardRank,
- interactedSubCardCardinality
+ interactedSubCardCardinality,
)
}
return
@@ -437,7 +405,7 @@ constructor(
surface,
index,
interactedSubCardRank,
- interactedSubCardCardinality
+ interactedSubCardCardinality,
)
}
return
@@ -459,14 +427,14 @@ constructor(
SMARTSPACE_CARD_DISMISS_EVENT,
surface,
mediaCommonModel.mediaLoadedModel.isSsReactivated,
- isSwipeToDismiss = true
+ isSwipeToDismiss = true,
)
is MediaCommonModel.MediaRecommendations ->
logSmarspaceRecommendationCardUserEvent(
SMARTSPACE_CARD_DISMISS_EVENT,
surface,
index,
- isSwipeToDismiss = true
+ isSwipeToDismiss = true,
)
}
}
@@ -513,7 +481,7 @@ constructor(
isReactivated: Boolean,
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
- isSwipeToDismiss: Boolean = false
+ isSwipeToDismiss: Boolean = false,
) {
_selectedUserEntries.value[instanceId]?.let {
smartspaceLogger.logSmartspaceCardUIEvent(
@@ -537,7 +505,7 @@ constructor(
index: Int,
interactedSubCardRank: Int = 0,
interactedSubCardCardinality: Int = 0,
- isSwipeToDismiss: Boolean = false
+ isSwipeToDismiss: Boolean = false,
) {
smartspaceLogger.logSmartspaceCardUIEvent(
eventId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 1f339dddd4d1..09aa85b74d2a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -68,8 +68,6 @@ constructor(
.map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } }
.distinctUntilChanged()
- val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
fun removeMediaControl(
token: MediaSession.Token?,
instanceId: InstanceId,
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
index c3a36b258842..48ed3915dedd 100644
--- 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
@@ -66,14 +66,12 @@ constructor(
.distinctUntilChanged()
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
- val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange
-
fun removeMediaRecommendations(
key: String,
dismissIntent: Intent?,
delayMs: Long,
eventId: Int,
- location: Int
+ location: Int,
) {
logSmartspaceCardUserEvent(eventId, location)
mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
@@ -101,7 +99,7 @@ constructor(
eventId: Int,
location: Int,
interactedSubCardRank: Int,
- interactedSubCardCardinality: Int
+ interactedSubCardCardinality: Int,
) {
if (interactedSubCardRank == -1) {
logSmartspaceCardUserEvent(eventId, MediaSmartspaceLogger.getSurface(location))
@@ -111,7 +109,7 @@ constructor(
MediaSmartspaceLogger.getSurface(location),
interactedSubCardRank = interactedSubCardRank,
interactedSubCardCardinality = interactedSubCardCardinality,
- isRec = true
+ isRec = true,
)
}
if (shouldActivityOpenInForeground(intent)) {
@@ -121,7 +119,7 @@ constructor(
0 /* delay */,
expandable.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
- )
+ ),
)
} else {
// Otherwise, open the activity in background directly.
@@ -133,7 +131,7 @@ constructor(
repository.logSmartspaceCardUserEvent(
eventId,
MediaSmartspaceLogger.getSurface(location),
- isRec = true
+ isRec = true,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index a6e1582d4e0c..910d3a84aeae 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -34,6 +34,7 @@ import android.widget.ImageButton
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.widget.AdaptiveIcon
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -64,7 +65,6 @@ import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig
import com.android.systemui.surfaceeffects.ripple.RippleShader
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 = "MediaControlViewBinder"
@@ -85,7 +85,7 @@ object MediaControlViewBinder {
launch {
viewModel.player.collectLatest { player ->
player?.let {
- if (viewModel.isNewPlayer(it)) {
+ if (viewModel.setPlayer(it)) {
bindMediaCard(
viewHolder,
viewController,
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 43c20117b6e0..f0f8a9592b6f 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
@@ -37,6 +37,7 @@ import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DiffUtil
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -115,7 +116,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "MediaCarouselController"
@@ -752,7 +752,11 @@ constructor(
}
}
- private fun onAdded(commonViewModel: MediaCommonViewModel, position: Int) {
+ private fun onAdded(
+ commonViewModel: MediaCommonViewModel,
+ position: Int,
+ configChanged: Boolean = false,
+ ) {
val viewController = mediaViewControllerFactory.get()
viewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
@@ -763,12 +767,13 @@ constructor(
when (commonViewModel) {
is MediaCommonViewModel.MediaControl -> {
val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- if (SceneContainerFlag.isEnabled) {
- viewController.widthInSceneContainerPx = widthInSceneContainerPx
- viewController.heightInSceneContainerPx = heightInSceneContainerPx
- }
+ viewController.widthInSceneContainerPx = widthInSceneContainerPx
+ viewController.heightInSceneContainerPx = heightInSceneContainerPx
viewController.attachPlayer(viewHolder)
viewController.mediaViewHolder?.player?.layoutParams = lp
+ if (configChanged) {
+ commonViewModel.controlViewModel.onMediaConfigChanged()
+ }
MediaControlViewBinder.bind(
viewHolder,
commonViewModel.controlViewModel,
@@ -1271,23 +1276,14 @@ constructor(
ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
if (recreateMedia) {
mediaContent.removeAllViews()
- commonViewModels.forEach { viewModel ->
+ commonViewModels.forEachIndexed { index, viewModel ->
when (viewModel) {
- is MediaCommonViewModel.MediaControl -> {
- controllerById[viewModel.instanceId.toString()]?.let {
- it.widthInSceneContainerPx = widthInSceneContainerPx
- it.heightInSceneContainerPx = heightInSceneContainerPx
- mediaContent.addView(it.mediaViewHolder?.player)
- }
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- controllerById[viewModel.key]?.let {
- it.widthInSceneContainerPx = widthInSceneContainerPx
- it.heightInSceneContainerPx = heightInSceneContainerPx
- mediaContent.addView(it.recommendationViewHolder?.recommendations)
- }
- }
+ is MediaCommonViewModel.MediaControl ->
+ controllerById[viewModel.instanceId.toString()]?.onDestroy()
+ is MediaCommonViewModel.MediaRecommendations ->
+ controllerById[viewModel.key]?.onDestroy()
}
+ onAdded(viewModel, index, configChanged = true)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
index c21513b1263a..14a4e2656d7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
@@ -98,7 +98,11 @@ object MediaArtworkHelper {
}
/** Returns [ColorScheme] of media app given its [icon]. */
- fun getColorScheme(icon: Drawable, tag: String, style: Style = Style.TONAL_SPOT): ColorScheme? {
+ fun getColorScheme(
+ icon: Drawable,
+ tag: String,
+ @Style.Type style: Int = Style.TONAL_SPOT,
+ ): ColorScheme? {
return try {
ColorScheme(WallpaperColors.fromDrawable(icon), true, style)
} catch (e: PackageManager.NameNotFoundException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 4173d2aa272e..4e97f2015c12 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -41,10 +41,8 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.res.R
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -56,15 +54,9 @@ class MediaControlViewModel(
private val interactor: MediaControlInteractor,
private val logger: MediaUiEventLogger,
) {
-
- @OptIn(ExperimentalCoroutinesApi::class)
val player: Flow<MediaPlayerViewModel?> =
- interactor.onAnyMediaConfigurationChange
- .flatMapLatest {
- interactor.mediaControl.map { mediaControl ->
- mediaControl?.let { toViewModel(it) }
- }
- }
+ interactor.mediaControl
+ .map { mediaControl -> mediaControl?.let { toViewModel(it) } }
.distinctUntilChanged { old, new ->
(new == null && old == null) || new?.contentEquals(old) ?: false
}
@@ -74,14 +66,21 @@ class MediaControlViewModel(
private var isAnyButtonClicked = false
@MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN
private var playerViewModel: MediaPlayerViewModel? = null
+ private var allowPlayerUpdate: Boolean = false
+
+ fun setPlayer(viewModel: MediaPlayerViewModel): Boolean {
+ val tempViewModel = playerViewModel
+ playerViewModel = viewModel
+ return allowPlayerUpdate || !(tempViewModel?.contentEquals(viewModel) ?: false)
+ }
- fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean {
- val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false
- return (!contentEquals).also { playerViewModel = viewModel }
+ fun onMediaConfigChanged() {
+ allowPlayerUpdate = true
}
fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) {
interactor.logMediaControlIsBound(artistName, titleName)
+ allowPlayerUpdate = false
}
private fun onDismissMediaData(
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
index 6bc6b10a1dfd..88cfbaf00987 100644
--- 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
@@ -41,10 +41,8 @@ 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -59,12 +57,9 @@ constructor(
private val logger: MediaUiEventLogger,
) {
- @OptIn(ExperimentalCoroutinesApi::class)
val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
- interactor.onAnyMediaConfigurationChange
- .flatMapLatest {
- interactor.recommendations.map { recsCard -> toRecsViewModel(recsCard) }
- }
+ interactor.recommendations
+ .map { recsCard -> toRecsViewModel(recsCard) }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
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 b69b25dbddf2..8fbbb8bc4872 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -813,8 +813,15 @@ public class MediaSwitchingController
}
private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) {
+ boolean isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() == 1;
+ if (enableInputRouting()) {
+ // When input routing is enabled, there are expected to be at least 2 total selected
+ // devices: one output device and one input device.
+ isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() <= 2;
+ }
+
// Attach "Connect a device" item only when current output is not remote and not a group
- if (!isCurrentConnectedDeviceRemote() && getSelectedMediaDevice().size() == 1) {
+ if (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup) {
mediaItems.add(MediaItem.createPairNewDeviceMediaItem());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 32de56f93427..e17255a7c2f0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -50,22 +50,29 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
@ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
) : DialogDelegate<T>, AdapterView.OnItemSelectedListener {
private lateinit var dialogTitle: TextView
- private lateinit var startButton: TextView
private lateinit var cancelButton: TextView
- private lateinit var warning: TextView
private lateinit var screenShareModeSpinner: Spinner
protected lateinit var dialog: AlertDialog
- private var shouldLogCancel: Boolean = true
- var selectedScreenShareOption: ScreenShareOption =
- screenShareOptions.first { it.mode == defaultSelectedMode }
+ protected lateinit var viewBinder: BaseMediaProjectionPermissionViewBinder
+
+ /**
+ * Create the view binder for the permission dialog, this can be override by child classes to
+ * support a different type of view binder
+ */
+ open fun createViewBinder(): BaseMediaProjectionPermissionViewBinder {
+ return BaseMediaProjectionPermissionViewBinder(
+ screenShareOptions,
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger,
+ defaultSelectedMode,
+ dialog,
+ )
+ }
@CallSuper
override fun onStop(dialog: T) {
- // onStop can be called multiple times and we only want to log once.
- if (shouldLogCancel) {
- mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
- shouldLogCancel = false
- }
+ viewBinder.unbind()
}
@CallSuper
@@ -75,12 +82,14 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
dialog.window?.setGravity(Gravity.CENTER)
dialog.setContentView(R.layout.screen_share_dialog)
dialogTitle = dialog.requireViewById(R.id.screen_share_dialog_title)
- warning = dialog.requireViewById(R.id.text_warning)
- startButton = dialog.requireViewById(android.R.id.button1)
cancelButton = dialog.requireViewById(android.R.id.button2)
updateIcon()
- initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
+ if (!::viewBinder.isInitialized) {
+ viewBinder = createViewBinder()
+ }
+ viewBinder.bind()
+ initScreenShareSpinner()
}
private fun updateIcon() {
@@ -93,18 +102,6 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
}
}
- private fun initScreenShareOptions() {
- selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
- setOptionSpecificFields()
- initScreenShareSpinner()
- }
-
- private val warningText: String
- get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
-
- private val startButtonText: String
- get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
-
private fun initScreenShareSpinner() {
val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
@@ -128,18 +125,15 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
}
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
- selectedScreenShareOption = screenShareOptions[pos]
- setOptionSpecificFields()
- }
-
- /** Sets fields on the dialog that change based on which option is selected. */
- private fun setOptionSpecificFields() {
- warning.text = warningText
- startButton.text = startButtonText
+ viewBinder.onItemSelected(pos)
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
+ fun getSelectedScreenShareOption(): ScreenShareOption {
+ return viewBinder.selectedScreenShareOption
+ }
+
/** Protected methods for the text updates & functionality */
protected fun setDialogTitle(@StringRes stringId: Int) {
val title = dialog.context.getString(stringId, appName)
@@ -147,10 +141,7 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
}
protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
- startButton.setOnClickListener { view ->
- shouldLogCancel = false
- listener?.onClick(view)
- }
+ viewBinder.setStartButtonOnClickListener(listener)
}
protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
new file mode 100644
index 000000000000..728255d168f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.view.View
+import android.widget.TextView
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+
+open class BaseMediaProjectionPermissionViewBinder(
+ private val screenShareOptions: List<ScreenShareOption>,
+ private val appName: String?,
+ private val hostUid: Int,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ @ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
+ private val dialog: AlertDialog,
+) {
+ private lateinit var warning: TextView
+ private lateinit var startButton: TextView
+ var selectedScreenShareOption: ScreenShareOption =
+ screenShareOptions.first { it.mode == defaultSelectedMode }
+ private var shouldLogCancel: Boolean = true
+
+ fun unbind() {
+ // unbind can be called multiple times and we only want to log once.
+ if (shouldLogCancel) {
+ mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+ shouldLogCancel = false
+ }
+ }
+
+ open fun bind() {
+ warning = dialog.requireViewById(R.id.text_warning)
+ startButton = dialog.requireViewById(android.R.id.button1)
+ initScreenShareOptions()
+ }
+
+ private fun initScreenShareOptions() {
+ selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
+ setOptionSpecificFields()
+ }
+
+ /** Sets fields on the dialog that change based on which option is selected. */
+ private fun setOptionSpecificFields() {
+ warning.text = warningText
+ startButton.text = startButtonText
+ }
+
+ open fun onItemSelected(pos: Int) {
+ selectedScreenShareOption = screenShareOptions[pos]
+ setOptionSpecificFields()
+ }
+
+ private val warningText: String
+ get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
+
+ private val startButtonText: String
+ get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
+
+ fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
+ startButton.setOnClickListener { view ->
+ shouldLogCancel = false
+ listener?.onClick(view)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index debb667bbb15..a19c9b30f68d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.mediarouter.data.repository
+import android.media.projection.StopReason
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
@@ -40,7 +41,7 @@ interface MediaRouterRepository {
val castDevices: StateFlow<List<CastDevice>>
/** Stops the cast to the given device. */
- fun stopCasting(device: CastDevice)
+ fun stopCasting(device: CastDevice, @StopReason stopReason: Int)
}
@SysUISingleton
@@ -67,8 +68,8 @@ constructor(
.map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
- override fun stopCasting(device: CastDevice) {
- castController.stopCasting(device)
+ override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
+ castController.stopCasting(device, stopReason)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index a3b7590117c1..d2b1d5449aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -56,7 +56,7 @@ import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.Utils;
@@ -124,7 +124,7 @@ public class NavigationBarControllerImpl implements
TaskbarDelegate taskbarDelegate,
NavigationBarComponent.Factory navigationBarComponentFactory,
DumpManager dumpManager,
- AutoHideController autoHideController,
+ AutoHideControllerStore autoHideControllerStore,
LightBarController lightBarController,
TaskStackChangeListeners taskStackChangeListeners,
Optional<Pip> pipOptional,
@@ -146,8 +146,9 @@ public class NavigationBarControllerImpl implements
mTaskbarDelegate = taskbarDelegate;
mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navBarHelper, navigationModeController, sysUiFlagsContainer,
- dumpManager, autoHideController, lightBarController, pipOptional,
- backAnimation.orElse(null), taskStackChangeListeners);
+ dumpManager, autoHideControllerStore.forDisplay(mContext.getDisplayId()),
+ lightBarController, pipOptional, backAnimation.orElse(null),
+ taskStackChangeListeners);
mIsLargeScreen = isLargeScreen(mContext);
mIsPhone = determineIfPhone(mContext, deviceStateManager);
dumpManager.registerDumpable(this);
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 0a4e8c660761..b1719107fae1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -68,6 +68,7 @@ import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.window.BackEvent;
import androidx.annotation.DimenRes;
@@ -585,6 +586,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mNonLinearFactor = getDimenFloat(res,
com.android.internal.R.dimen.back_progress_non_linear_factor);
updateBackAnimationThresholds();
+ mBackgroundExecutor.execute(this::disableNavBarVirtualKeyHapticFeedback);
}
private float getDimenFloat(Resources res, @DimenRes int resId) {
@@ -1287,6 +1289,15 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
}
+ private void disableNavBarVirtualKeyHapticFeedback() {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setNavBarVirtualKeyHapticFeedbackEnabled(false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to disable navigation bar button haptics: ", e);
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("EdgeBackGestureHandler:");
pw.println(" mIsEnabled=" + mIsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac53908..c895732f79f6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -151,6 +151,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -261,8 +262,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final LightBarController mMainLightBarController;
private final LightBarController.Factory mLightBarControllerFactory;
private AutoHideController mAutoHideController;
- private final AutoHideController mMainAutoHideController;
- private final AutoHideController.Factory mAutoHideControllerFactory;
+ private final AutoHideControllerStore mAutoHideControllerStore;
private final Optional<TelecomManager> mTelecomManagerOptional;
private final InputMethodManager mInputMethodManager;
private final TaskStackChangeListeners mTaskStackChangeListeners;
@@ -582,8 +582,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
NavBarHelper navBarHelper,
LightBarController mainLightBarController,
LightBarController.Factory lightBarControllerFactory,
- AutoHideController mainAutoHideController,
- AutoHideController.Factory autoHideControllerFactory,
+ AutoHideControllerStore autoHideControllerStore,
Optional<TelecomManager> telecomManagerOptional,
InputMethodManager inputMethodManager,
DeadZone deadZone,
@@ -630,8 +629,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mNotificationShadeDepthController = notificationShadeDepthController;
mMainLightBarController = mainLightBarController;
mLightBarControllerFactory = lightBarControllerFactory;
- mMainAutoHideController = mainAutoHideController;
- mAutoHideControllerFactory = autoHideControllerFactory;
+ mAutoHideControllerStore = autoHideControllerStore;
mTelecomManagerOptional = telecomManagerOptional;
mInputMethodManager = inputMethodManager;
mUserContextProvider = userContextProvider;
@@ -846,13 +844,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
? mMainLightBarController : mLightBarControllerFactory.create(mContext);
setLightBarController(lightBarController);
- // TODO(b/118592525): to support multi-display, we start to add something which is
- // per-display, while others may be global. I think it's time to
- // add a new class maybe named DisplayDependency to solve
- // per-display Dependency problem.
- // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController.
- AutoHideController autoHideController = mIsOnDefaultDisplay
- ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext);
+ AutoHideController autoHideController = mAutoHideControllerStore.forDisplay(mDisplayId);
setAutoHideController(autoHideController);
restoreAppearanceAndTransientState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index eff5fc0db761..d8fc52bcc55a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -216,20 +216,13 @@ constructor(
"handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
event.keycodes.contentToString()
}
- if (
- event.keycodes.contains(KEYCODE_N) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
- ) {
- debugLog { "Note task triggered by keyboard shortcut" }
- backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
- return true
- }
if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
debugLog { "Note task triggered by stylus tail button" }
backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
return true
}
- return false
+ backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
+ return true
}
private fun isKeyGestureSupported(gestureType: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 2a5ffc6cc391..5c9baa000d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -55,8 +55,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
-import com.android.systemui.Flags;
-import com.android.systemui.res.R
+import com.android.systemui.Flags
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -65,7 +64,9 @@ 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.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.DeviceConfigProxy
@@ -106,8 +107,8 @@ interface FgsManagerController {
fun init()
/**
- * Show the foreground services dialog. The dialog will be expanded from [expandable] if
- * it's not `null`.
+ * Show the foreground services dialog. The dialog will be expanded from [expandable] if it's
+ * not `null`.
*/
fun showDialog(expandable: Expandable?)
@@ -123,8 +124,7 @@ interface FgsManagerController {
/** Remove a [OnDialogDismissedListener]. */
fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
- @VisibleForTesting
- fun visibleButtonsCount(): Int
+ @VisibleForTesting fun visibleButtonsCount(): Int
interface OnNumberOfPackagesChangedListener {
/** Called when [numRunningPackages] changed. */
@@ -138,8 +138,10 @@ interface FgsManagerController {
}
@SysUISingleton
-class FgsManagerControllerImpl @Inject constructor(
- @Main private val resources: Resources,
+class FgsManagerControllerImpl
+@Inject
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
@Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
private val systemClock: SystemClock,
@@ -187,50 +189,46 @@ class FgsManagerControllerImpl @Inject constructor(
private val lock = Any()
- @GuardedBy("lock")
- var initialized = false
+ @GuardedBy("lock") var initialized = false
- @GuardedBy("lock")
- private var lastNumberOfVisiblePackages = 0
+ @GuardedBy("lock") private var lastNumberOfVisiblePackages = 0
- @GuardedBy("lock")
- private var currentProfileIds = mutableSetOf<Int>()
+ @GuardedBy("lock") private var currentProfileIds = mutableSetOf<Int>()
@GuardedBy("lock")
private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
- @GuardedBy("lock")
- private var dialog: SystemUIDialog? = null
+ @GuardedBy("lock") private var dialog: SystemUIDialog? = null
- @GuardedBy("lock")
- private val appListAdapter: AppListAdapter = AppListAdapter()
+ @GuardedBy("lock") private val appListAdapter: AppListAdapter = AppListAdapter()
/* Only mutate on the background thread */
private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {}
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {}
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- synchronized(lock) {
- currentProfileIds.clear()
- currentProfileIds.addAll(profiles.map { it.id })
- lastNumberOfVisiblePackages = 0
- updateNumberOfVisibleRunningPackagesLocked()
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ synchronized(lock) {
+ currentProfileIds.clear()
+ currentProfileIds.addAll(profiles.map { it.id })
+ lastNumberOfVisiblePackages = 0
+ updateNumberOfVisibleRunningPackagesLocked()
+ }
}
}
- }
private val foregroundServiceObserver = ForegroundServiceObserver()
private val userVisibleJobObserver = UserVisibleJobObserver()
- private val stoppableApps by lazy { resources
- .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
+ private val stoppableApps by lazy {
+ resources.getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
}
- private val vendorStoppableApps by lazy { resources
- .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
+ private val vendorStoppableApps by lazy {
+ resources.getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
}
override fun init() {
@@ -239,14 +237,19 @@ class FgsManagerControllerImpl @Inject constructor(
return
}
- showUserVisibleJobs = deviceConfigProxy.getBoolean(
- NAMESPACE_SYSTEMUI,
- TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
+ showUserVisibleJobs =
+ deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+ DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+ )
- informJobSchedulerOfPendingAppStop = deviceConfigProxy.getBoolean(
- NAMESPACE_SYSTEMUI,
- TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
- DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP)
+ informJobSchedulerOfPendingAppStop =
+ deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+ DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+ )
try {
activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
@@ -267,31 +270,39 @@ class FgsManagerControllerImpl @Inject constructor(
deviceConfigProxy.addOnPropertiesChangedListener(
NAMESPACE_SYSTEMUI,
- backgroundExecutor
+ backgroundExecutor,
) {
_showFooterDot.value =
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
- showStopBtnForUserAllowlistedApps = it.getBoolean(
- TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
- showStopBtnForUserAllowlistedApps)
+ showStopBtnForUserAllowlistedApps =
+ it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ showStopBtnForUserAllowlistedApps,
+ )
var wasShowingUserVisibleJobs = showUserVisibleJobs
- showUserVisibleJobs = it.getBoolean(
- TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+ showUserVisibleJobs =
+ it.getBoolean(TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
onShowUserVisibleJobsFlagChanged()
}
- informJobSchedulerOfPendingAppStop = it.getBoolean(
- TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
- informJobSchedulerOfPendingAppStop)
+ informJobSchedulerOfPendingAppStop =
+ it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ informJobSchedulerOfPendingAppStop,
+ )
}
- _showFooterDot.value = deviceConfigProxy.getBoolean(
- NAMESPACE_SYSTEMUI,
- TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
- )
- showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
- NAMESPACE_SYSTEMUI,
- TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
- DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
+ _showFooterDot.value =
+ deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_FOOTER_DOT,
+ DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT,
+ )
+ showStopBtnForUserAllowlistedApps =
+ deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ )
dumpManager.registerDumpable(this)
@@ -305,7 +316,7 @@ class FgsManagerControllerImpl @Inject constructor(
},
IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER),
executor = mainExecutor,
- flags = Context.RECEIVER_NOT_EXPORTED
+ flags = Context.RECEIVER_NOT_EXPORTED,
)
initialized = true
@@ -323,33 +334,25 @@ class FgsManagerControllerImpl @Inject constructor(
override fun addOnNumberOfPackagesChangedListener(
listener: FgsManagerController.OnNumberOfPackagesChangedListener
) {
- synchronized(lock) {
- onNumberOfPackagesChangedListeners.add(listener)
- }
+ synchronized(lock) { onNumberOfPackagesChangedListeners.add(listener) }
}
override fun removeOnNumberOfPackagesChangedListener(
listener: FgsManagerController.OnNumberOfPackagesChangedListener
) {
- synchronized(lock) {
- onNumberOfPackagesChangedListeners.remove(listener)
- }
+ synchronized(lock) { onNumberOfPackagesChangedListeners.remove(listener) }
}
override fun addOnDialogDismissedListener(
listener: FgsManagerController.OnDialogDismissedListener
) {
- synchronized(lock) {
- onDialogDismissedListeners.add(listener)
- }
+ synchronized(lock) { onDialogDismissedListeners.add(listener) }
}
override fun removeOnDialogDismissedListener(
listener: FgsManagerController.OnDialogDismissedListener
) {
- synchronized(lock) {
- onDialogDismissedListeners.remove(listener)
- }
+ synchronized(lock) { onDialogDismissedListeners.remove(listener) }
}
private fun getNumVisiblePackagesLocked(): Int {
@@ -364,9 +367,7 @@ class FgsManagerControllerImpl @Inject constructor(
lastNumberOfVisiblePackages = num
newChangesSinceDialogWasDismissed = true
onNumberOfPackagesChangedListeners.forEach {
- backgroundExecutor.execute {
- it.onNumberOfPackagesChanged(num)
- }
+ backgroundExecutor.execute { it.onNumberOfPackagesChanged(num) }
}
}
}
@@ -396,8 +397,10 @@ class FgsManagerControllerImpl @Inject constructor(
recyclerView.layoutManager = LinearLayoutManager(dialogContext)
recyclerView.adapter = appListAdapter
- val topSpacing = dialogContext.resources
- .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing)
+ val topSpacing =
+ dialogContext.resources.getDimensionPixelSize(
+ R.dimen.fgs_manager_list_top_spacing
+ )
dialog.setView(recyclerView, 0, topSpacing, 0, 0)
this.dialog = dialog
@@ -436,9 +439,7 @@ class FgsManagerControllerImpl @Inject constructor(
@GuardedBy("lock")
private fun updateAppItemsLocked(refreshUiControls: Boolean = false) {
if (dialog == null) {
- backgroundExecutor.execute {
- clearRunningApps()
- }
+ backgroundExecutor.execute { clearRunningApps() }
return
}
@@ -449,59 +450,61 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
- /**
- * Must be called on the background thread.
- */
+ /** Must be called on the background thread. */
@WorkerThread
private fun updateAppItems(
packages: Map<UserPackage, Long>,
profileIds: Set<Int>,
- refreshUiControls: Boolean = true
+ refreshUiControls: Boolean = true,
) {
if (refreshUiControls) {
- packages.forEach { (pkg, _) ->
- pkg.updateUiControl()
- }
+ packages.forEach { (pkg, _) -> pkg.updateUiControl() }
}
- val addedPackages = packages.keys.filter {
- profileIds.contains(it.userId) &&
- it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
- }
+ val addedPackages =
+ packages.keys.filter {
+ profileIds.contains(it.userId) &&
+ it.uiControl != UIControl.HIDE_ENTRY &&
+ runningApps[it]?.stopped != true
+ }
val removedPackages = runningApps.keys.filter { it !in packages }
addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
- runningApps[it] = RunningApp(
- it.userId, it.packageName,
- packages[it]!!, it.uiControl,
- packageManager.getApplicationLabel(ai),
- packageManager.getUserBadgedIcon(
- packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
+ runningApps[it] =
+ RunningApp(
+ it.userId,
+ it.packageName,
+ packages[it]!!,
+ it.uiControl,
+ packageManager.getApplicationLabel(ai),
+ packageManager.getUserBadgedIcon(
+ packageManager.getApplicationIcon(ai),
+ UserHandle.of(it.userId),
+ ),
)
- )
logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
}
removedPackages.forEach { pkg ->
val ra = runningApps[pkg]!!
- val ra2 = ra.copy().also {
- it.stopped = true
- it.appLabel = ra.appLabel
- it.icon = ra.icon
- }
+ val ra2 =
+ ra.copy().also {
+ it.stopped = true
+ it.appLabel = ra.appLabel
+ it.icon = ra.icon
+ }
runningApps[pkg] = ra2
}
mainExecutor.execute {
- appListAdapter
- .setData(runningApps.values.toList().sortedByDescending { it.timeStarted })
+ appListAdapter.setData(
+ runningApps.values.toList().sortedByDescending { it.timeStarted }
+ )
}
}
- /**
- * Must be called on the background thread.
- */
+ /** Must be called on the background thread. */
@WorkerThread
private fun clearRunningApps() {
runningApps.clear()
@@ -545,16 +548,19 @@ class FgsManagerControllerImpl @Inject constructor(
private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
val timeLogged = systemClock.elapsedRealtime()
- val event = if (stopped) {
- SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED
- } else {
- SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED
- }
+ val event =
+ if (stopped) {
+ SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED
+ } else {
+ SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED
+ }
backgroundExecutor.execute {
val uid = packageManager.getPackageUidAsUser(packageName, userId)
SysUiStatsLog.write(
- SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event,
- timeLogged - timeStarted
+ SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED,
+ uid,
+ event,
+ timeLogged - timeStarted,
)
}
}
@@ -562,8 +568,7 @@ class FgsManagerControllerImpl @Inject constructor(
private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
private val lock = Any()
- @GuardedBy("lock")
- private var data: List<RunningApp> = listOf()
+ @GuardedBy("lock") private var data: List<RunningApp> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
return AppItemViewHolder(
@@ -574,16 +579,15 @@ class FgsManagerControllerImpl @Inject constructor(
override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
var runningApp: RunningApp
- synchronized(lock) {
- runningApp = data[position]
- }
+ synchronized(lock) { runningApp = data[position] }
with(holder) {
iconView.setImageDrawable(runningApp.icon)
appLabelView.text = runningApp.appLabel
- durationView.text = DateUtils.formatDuration(
- max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
- DateUtils.LENGTH_MEDIUM
- )
+ durationView.text =
+ DateUtils.formatDuration(
+ max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
+ DateUtils.LENGTH_MEDIUM,
+ )
stopButton.setOnClickListener {
stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted)
@@ -611,46 +615,54 @@ class FgsManagerControllerImpl @Inject constructor(
var oldData = data
data = newData
- DiffUtil.calculateDiff(object : DiffUtil.Callback() {
- override fun getOldListSize(): Int {
- return oldData.size
- }
+ DiffUtil.calculateDiff(
+ object : DiffUtil.Callback() {
+ override fun getOldListSize(): Int {
+ return oldData.size
+ }
- override fun getNewListSize(): Int {
- return newData.size
- }
+ override fun getNewListSize(): Int {
+ return newData.size
+ }
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return oldData[oldItemPosition] == newData[newItemPosition]
- }
+ override fun areItemsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int,
+ ): Boolean {
+ return oldData[oldItemPosition] == newData[newItemPosition]
+ }
- override fun areContentsTheSame(
- oldItemPosition: Int,
- newItemPosition: Int
- ): Boolean {
- return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped
- }
- }).dispatchUpdatesTo(this)
+ override fun areContentsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int,
+ ): Boolean {
+ return oldData[oldItemPosition].stopped ==
+ newData[newItemPosition].stopped
+ }
+ }
+ )
+ .dispatchUpdatesTo(this)
}
}
private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() {
override fun onForegroundStateChanged(
- token: IBinder,
- packageName: String,
- userId: Int,
- isForeground: Boolean
+ token: IBinder,
+ packageName: String,
+ userId: Int,
+ isForeground: Boolean,
) {
synchronized(lock) {
val userPackageKey = UserPackage(userId, packageName)
if (isForeground) {
runningTaskIdentifiers
- .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
- .addFgsToken(token)
+ .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+ .addFgsToken(token)
} else {
- if (runningTaskIdentifiers[userPackageKey]?.also {
- it.removeFgsToken(token)
- }?.isEmpty() == true
+ if (
+ runningTaskIdentifiers[userPackageKey]
+ ?.also { it.removeFgsToken(token) }
+ ?.isEmpty() == true
) {
runningTaskIdentifiers.remove(userPackageKey)
}
@@ -665,20 +677,24 @@ class FgsManagerControllerImpl @Inject constructor(
private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() {
override fun onUserVisibleJobStateChanged(
- summary: UserVisibleJobSummary,
- isRunning: Boolean
+ summary: UserVisibleJobSummary,
+ isRunning: Boolean,
) {
synchronized(lock) {
- val userPackageKey = UserPackage(
- UserHandle.getUserId(summary.callingUid), summary.callingPackageName)
+ val userPackageKey =
+ UserPackage(
+ UserHandle.getUserId(summary.callingUid),
+ summary.callingPackageName,
+ )
if (isRunning) {
runningTaskIdentifiers
- .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
- .addJobSummary(summary)
+ .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+ .addJobSummary(summary)
} else {
- if (runningTaskIdentifiers[userPackageKey]?.also {
- it.removeJobSummary(summary)
- }?.isEmpty() == true
+ if (
+ runningTaskIdentifiers[userPackageKey]
+ ?.also { it.removeJobSummary(summary) }
+ ?.isEmpty() == true
) {
runningTaskIdentifiers.remove(userPackageKey)
}
@@ -691,10 +707,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
- private inner class UserPackage(
- val userId: Int,
- val packageName: String
- ) {
+ private inner class UserPackage(val userId: Int, val packageName: String) {
val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) }
var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED
@@ -711,30 +724,31 @@ class FgsManagerControllerImpl @Inject constructor(
fun updateUiControl() {
backgroundRestrictionExemptionReason =
activityManager.getBackgroundRestrictionExemptionReason(uid)
- uiControl = when (backgroundRestrictionExemptionReason) {
- PowerExemptionManager.REASON_SYSTEM_UID,
- PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
-
- PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
- PowerExemptionManager.REASON_DEVICE_OWNER,
- PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
- PowerExemptionManager.REASON_DPO_PROTECTED_APP,
- PowerExemptionManager.REASON_PROFILE_OWNER,
- PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN,
- PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
- PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
- PowerExemptionManager.REASON_ROLE_DIALER,
- PowerExemptionManager.REASON_SYSTEM_MODULE,
- PowerExemptionManager.REASON_SYSTEM_EXEMPT_APP_OP -> UIControl.HIDE_BUTTON
-
- PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
- if (showStopBtnForUserAllowlistedApps) {
- UIControl.NORMAL
- } else {
- UIControl.HIDE_BUTTON
- }
- else -> UIControl.NORMAL
- }
+ uiControl =
+ when (backgroundRestrictionExemptionReason) {
+ PowerExemptionManager.REASON_SYSTEM_UID,
+ PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
+
+ PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
+ PowerExemptionManager.REASON_DEVICE_OWNER,
+ PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
+ PowerExemptionManager.REASON_DPO_PROTECTED_APP,
+ PowerExemptionManager.REASON_PROFILE_OWNER,
+ PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN,
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
+ PowerExemptionManager.REASON_ROLE_DIALER,
+ PowerExemptionManager.REASON_SYSTEM_MODULE,
+ PowerExemptionManager.REASON_SYSTEM_EXEMPT_APP_OP -> UIControl.HIDE_BUTTON
+
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+ if (showStopBtnForUserAllowlistedApps) {
+ UIControl.NORMAL
+ } else {
+ UIControl.HIDE_BUTTON
+ }
+ else -> UIControl.NORMAL
+ }
// If the app wants to be a good citizen by being stoppable, even if the category it
// belongs to is exempted for background restriction, let it be stoppable by user.
if (Flags.stoppableFgsSystemApp()) {
@@ -747,8 +761,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
fun isStoppableApp(packageName: String): Boolean {
- return stoppableApps.contains(packageName) ||
- vendorStoppableApps.contains(packageName)
+ return stoppableApps.contains(packageName) || vendorStoppableApps.contains(packageName)
}
override fun equals(other: Any?): Boolean {
@@ -771,9 +784,7 @@ class FgsManagerControllerImpl @Inject constructor(
}
}
- private data class StartTimeAndIdentifiers(
- val systemClock: SystemClock
- ) {
+ private data class StartTimeAndIdentifiers(val systemClock: SystemClock) {
val startTime = systemClock.elapsedRealtime()
val fgsTokens = mutableSetOf<IBinder>()
val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
@@ -846,7 +857,7 @@ class FgsManagerControllerImpl @Inject constructor(
val userId: Int,
val packageName: String,
val timeStarted: Long,
- val uiControl: UIControl
+ val uiControl: UIControl,
) {
constructor(
userId: Int,
@@ -854,7 +865,7 @@ class FgsManagerControllerImpl @Inject constructor(
timeStarted: Long,
uiControl: UIControl,
appLabel: CharSequence,
- icon: Drawable
+ icon: Drawable,
) : this(userId, packageName, timeStarted, uiControl) {
this.appLabel = appLabel
this.icon = icon
@@ -884,7 +895,9 @@ class FgsManagerControllerImpl @Inject constructor(
}
private enum class UIControl {
- NORMAL, HIDE_BUTTON, HIDE_ENTRY
+ NORMAL,
+ HIDE_BUTTON,
+ HIDE_ENTRY,
}
override fun dump(printwriter: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7ccdf0ac301a..946eccd05177 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -65,7 +66,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
QSContainerImpl view,
QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
FalsingManager falsingManager) {
super(view);
mQsPanelController = qsPanelController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ba3357c8b591..7c7f48e0d37b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -19,6 +19,8 @@ import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;
+import androidx.annotation.NonNull;
+
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.res.R;
@@ -80,6 +82,12 @@ public interface QSHost {
void addTile(ComponentName tile);
/**
+ * Click on a tile. Used by external commands
+ * @param tile the component name of the {@link android.service.quicksettings.TileService}
+ */
+ void clickTile(@NonNull ComponentName tile);
+
+ /**
* Adds a custom tile to the set of current tiles.
* @param tile the component name of the {@link android.service.quicksettings.TileService}
* @param end if true, the tile will be added at the end. If false, at the beginning.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 0d464f5a0936..dc3b58247152 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -19,11 +19,13 @@ package com.android.systemui.qs
import android.content.ComponentName
import android.content.Context
import androidx.annotation.GuardedBy
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.TileServiceRequestController
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
@@ -32,7 +34,6 @@ import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Adapter to determine what real class to use for classes that depend on [QSHost].
@@ -135,4 +136,12 @@ constructor(
override fun indexOf(tileSpec: String): Int {
return specs.indexOf(tileSpec)
}
+
+ override fun clickTile(tile: ComponentName) {
+ if (QsInCompose.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+ val spec = TileSpec.create(tile)
+ interactor.currentTiles.value.firstOrNull { it.spec == spec }?.tile?.click(null)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 5da480968b89..cb3fc071ac82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -10,6 +10,7 @@ import com.android.systemui.Prefs;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.shade.ShadeDisplayAware;
import java.util.Collection;
import java.util.Collections;
@@ -96,7 +97,7 @@ public class QSTileRevealController {
private final QSCustomizerController mQsCustomizerController;
@Inject
- Factory(Context context, QSCustomizerController qsCustomizerController) {
+ Factory(@ShadeDisplayAware Context context, QSCustomizerController qsCustomizerController) {
mContext = context;
mQsCustomizerController = qsCustomizerController;
}
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 5167d173647e..8d9f49e55cea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -47,7 +47,6 @@ 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.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
@@ -248,7 +247,7 @@ constructor(
PlatformTheme(isDarkTheme = true) {
ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
AnimatedVisibility(
- visible = viewModel.isQsVisible,
+ visible = viewModel.isQsVisibleAndAnyShadeExpanded,
modifier =
Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
// Clipping before translation to match QSContainerImpl.onDraw
@@ -629,11 +628,7 @@ constructor(
id = R.string.accessibility_quick_settings_expand
)
)
- .padding(
- horizontal = {
- QuickSettingsShade.Dimensions.Padding.roundToPx()
- }
- )
+ .padding(horizontal = qsHorizontalMargin())
) {
QuickQuickSettingsLayout(
tiles = Tiles,
@@ -713,12 +708,7 @@ constructor(
GridAnchor()
TileGrid(
viewModel = containerViewModel.tileGridViewModel,
- modifier =
- Modifier.fillMaxWidth()
- .heightIn(
- max =
- QuickSettingsShade.Dimensions.GridMaxHeight
- ),
+ modifier = Modifier.fillMaxWidth(),
)
}
}
@@ -737,8 +727,8 @@ constructor(
.sysuiResTag(ResIdTags.quickSettingsPanel)
.padding(
top = QuickSettingsShade.Dimensions.Padding,
- start = QuickSettingsShade.Dimensions.Padding,
- end = QuickSettingsShade.Dimensions.Padding,
+ start = qsHorizontalMargin(),
+ end = qsHorizontalMargin(),
)
) {
QuickSettingsLayout(
@@ -1127,6 +1117,8 @@ private object ResIdTags {
const val qsFooterActions = "qs_footer_actions"
}
+@Composable private fun qsHorizontalMargin() = dimensionResource(id = R.dimen.qs_horizontal_margin)
+
@Composable
private fun interactionsConfig() =
InteractionsConfig(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 02498d69b83d..3c725203a15f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -62,6 +62,7 @@ import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -101,6 +102,7 @@ constructor(
DisableFlagsInteractor: DisableFlagsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
+ private val shadeInteractor: ShadeInteractor,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val squishinessInteractor: TileSquishinessInteractor,
@@ -129,6 +131,9 @@ constructor(
var isQsVisible by mutableStateOf(false)
+ val isQsVisibleAndAnyShadeExpanded: Boolean
+ get() = anyShadeExpanded && isQsVisible
+
// This can only be negative if undefined (in which case it will be -1f), else it will be
// in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's
// different to every value in [0, 1].
@@ -429,6 +434,12 @@ constructor(
),
)
+ private val anyShadeExpanded by
+ hydrator.hydratedStateOf(
+ traceName = "anyShadeExpanded",
+ source = shadeInteractor.isAnyExpanded,
+ )
+
fun applyNewQsScrollerBounds(left: Float, top: Float, right: Float, bottom: Float) {
if (usingMedia) {
qsMediaHost.currentClipping.set(
@@ -503,6 +514,8 @@ constructor(
printSection("Quick Settings state") {
println("isQSExpanded", isQsExpanded)
println("isQSVisible", isQsVisible)
+ println("anyShadeExpanded", anyShadeExpanded)
+ println("isQSVisibleAndAnyShadeExpanded", isQsVisibleAndAnyShadeExpanded)
println("isQSEnabled", isQsEnabled)
println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value)
println("inFirstPage", inFirstPage)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index a222b3cbd08c..c606ce422d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -43,6 +43,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
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.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -107,7 +108,8 @@ public class QSCustomizerController extends ViewController<QSCustomizer> {
protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
KeyguardStateController keyguardStateController, LightBarController lightBarController,
- ConfigurationController configurationController, UiEventLogger uiEventLogger) {
+ @ShadeDisplayAware ConfigurationController configurationController,
+ UiEventLogger uiEventLogger) {
super(view);
mTileQueryHelper = tileQueryHelper;
mQsHost = qsHost;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
index 6f5dea32bd83..379b606dc49e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
@@ -22,12 +22,14 @@ import android.content.SharedPreferences
import android.service.quicksettings.Tile
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import org.json.JSONException
import org.json.JSONObject
data class TileServiceKey(val componentName: ComponentName, val user: Int) {
private val string = "${componentName.flattenToString()}:$user"
+
override fun toString() = string
}
@@ -56,12 +58,14 @@ interface CustomTileStatePersister {
* Any fields that have not been saved will be set to `null`
*/
fun readState(key: TileServiceKey): Tile?
+
/**
* Persists the state into [SharedPreferences].
*
* The implementation does not store fields that are `null` or icons.
*/
fun persistState(key: TileServiceKey, tile: Tile)
+
/**
* Removes the state for a given tile, user pair.
*
@@ -71,7 +75,7 @@ interface CustomTileStatePersister {
}
// TODO(b/299909989) Merge this class into into CustomTileRepository
-class CustomTileStatePersisterImpl @Inject constructor(context: Context) :
+class CustomTileStatePersisterImpl @Inject constructor(@Application context: Context) :
CustomTileStatePersister {
companion object {
private const val FILE_NAME = "custom_tiles_state"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
index 28e4fd0ef51c..6fb4455fecce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
@@ -28,6 +28,8 @@ import android.os.RemoteException;
import androidx.annotation.Nullable;
+import com.android.systemui.dagger.qualifiers.Application;
+
import javax.inject.Inject;
// Adapter that wraps calls to PackageManager or IPackageManager for {@link TileLifecycleManager}.
@@ -42,7 +44,7 @@ public class PackageManagerAdapter {
// Uses the PackageManager for the provided context.
// When necessary, uses the IPackagemanger in AppGlobals.
@Inject
- public PackageManagerAdapter(Context context) {
+ public PackageManagerAdapter(@Application Context context) {
mPackageManager = context.getPackageManager();
mIPackageManager = AppGlobals.getPackageManager();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
new file mode 100644
index 000000000000..de759687e012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.graphics.drawable.Icon
+import androidx.compose.runtime.Immutable
+
+/**
+ * Data bundle of information to show the user when requesting to add a TileService
+ *
+ * @property appName Name of the app requesting their [TileService] to be added.
+ * @property label Label of the tile.
+ * @property icon Icon for the tile.
+ */
+@Immutable
+data class TileData(
+ val callingUid: Int,
+ val appName: CharSequence,
+ val label: CharSequence,
+ val icon: Icon?,
+ val packageName: String,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index c3c587de5a24..5597f288e122 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,73 +18,73 @@ package com.android.systemui.qs.external
import android.app.IUriGrantsManager
import android.content.Context
-import android.graphics.drawable.Icon
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
-import com.android.systemui.res.R
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
-/**
- * Dialog to present to the user to ask for authorization to add a [TileService].
- */
-class TileRequestDialog(
- context: Context,
-) : SystemUIDialog(context) {
+/** Dialog to present to the user to ask for authorization to add a [TileService]. */
+class TileRequestDialog(context: Context) : SystemUIDialog(context) {
companion object {
internal val CONTENT_ID = R.id.content
}
- /**
- * Set the data of the tile to add, to show the user.
- */
+ /** Set the data of the tile to add, to show the user. */
fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
- val ll = (LayoutInflater
- .from(context)
- .inflate(R.layout.tile_service_request_dialog, null)
- as ViewGroup).apply {
+ val ll =
+ (LayoutInflater.from(context).inflate(R.layout.tile_service_request_dialog, null)
+ as ViewGroup)
+ .apply {
requireViewById<TextView>(R.id.text).apply {
- text = context
- .getString(R.string.qs_tile_request_dialog_text, tileData.appName)
+ text =
+ context.getString(
+ R.string.qs_tile_request_dialog_text,
+ tileData.appName,
+ )
}
addView(
- createTileView(tileData, iUriGrantsManager),
- context.resources.getDimensionPixelSize(
- R.dimen.qs_tile_service_request_tile_width),
- context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+ createTileView(tileData, iUriGrantsManager),
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_tile_service_request_tile_width
+ ),
+ context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size),
)
isSelected = true
- }
+ }
val spacing = 0
setView(ll, spacing, spacing, spacing, spacing / 2)
}
private fun createTileView(
- tileData: TileData,
- iUriGrantsManager: IUriGrantsManager,
+ tileData: TileData,
+ iUriGrantsManager: IUriGrantsManager,
): QSTileView {
val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
val tile = QSTileViewImpl(themedContext, true)
- val state = QSTile.BooleanState().apply {
- label = tileData.label
- handlesLongClick = false
- icon = tileData.icon?.loadDrawableCheckingUriGrant(
- context,
- iUriGrantsManager,
- tileData.callingUid,
- tileData.packageName,
- )?.let {
- QSTileImpl.DrawableIcon(it)
- } ?: ResourceIcon.get(R.drawable.android)
- contentDescription = label
- }
+ val state =
+ QSTile.BooleanState().apply {
+ label = tileData.label
+ handlesLongClick = false
+ icon =
+ tileData.icon
+ ?.loadDrawableCheckingUriGrant(
+ context,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )
+ ?.let { QSTileImpl.DrawableIcon(it) }
+ ?: ResourceIcon.get(R.drawable.android)
+ contentDescription = label
+ }
tile.onStateChanged(state)
tile.post {
tile.stateDescription = ""
@@ -93,19 +93,4 @@ class TileRequestDialog(
}
return tile
}
-
- /**
- * Data bundle of information to show the user.
- *
- * @property appName Name of the app requesting their [TileService] to be added.
- * @property label Label of the tile.
- * @property icon Icon for the tile.
- */
- data class TileData(
- val callingUid: Int,
- val appName: CharSequence,
- val label: CharSequence,
- val icon: Icon?,
- val packageName: String,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 08567afd729e..33e059074a81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -26,9 +26,11 @@ import android.os.RemoteException
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IAddTileResultCallback
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.TileRequestDialogComposeDelegate
+import com.android.systemui.qs.flags.QsInCompose
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -40,50 +42,50 @@ import javax.inject.Inject
private const val TAG = "TileServiceRequestController"
-/**
- * Controller to interface between [TileRequestDialog] and [QSHost].
- */
+/** Controller to interface between [TileRequestDialog] and [QSHost]. */
class TileServiceRequestController(
- private val qsHost: QSHost,
- private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry,
- private val eventLogger: TileRequestDialogEventLogger,
- private val iUriGrantsManager: IUriGrantsManager,
- private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+ private val qsHost: QSHost,
+ private val commandQueue: CommandQueue,
+ private val commandRegistry: CommandRegistry,
+ private val eventLogger: TileRequestDialogEventLogger,
+ private val iUriGrantsManager: IUriGrantsManager,
+ private val tileRequestDialogComposeDelegateFactory: TileRequestDialogComposeDelegate.Factory,
+ private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) },
) {
companion object {
- internal const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
- internal const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
- internal const val TILE_ALREADY_ADDED =
- StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
- internal const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+ const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+ const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+ const val TILE_ALREADY_ADDED =
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+ const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
}
private var dialogCanceller: ((String) -> Unit)? = null
- private val commandQueueCallback = object : CommandQueue.Callbacks {
- override fun requestAddTile(
- callingUid: Int,
- componentName: ComponentName,
- appName: CharSequence,
- label: CharSequence,
- icon: Icon,
- callback: IAddTileResultCallback
- ) {
- requestTileAdd(callingUid, componentName, appName, label, icon) {
- try {
- callback.onTileRequest(it)
- } catch (e: RemoteException) {
- Log.e(TAG, "Couldn't respond to request", e)
+ private val commandQueueCallback =
+ object : CommandQueue.Callbacks {
+ override fun requestAddTile(
+ callingUid: Int,
+ componentName: ComponentName,
+ appName: CharSequence,
+ label: CharSequence,
+ icon: Icon,
+ callback: IAddTileResultCallback,
+ ) {
+ requestTileAdd(callingUid, componentName, appName, label, icon) {
+ try {
+ callback.onTileRequest(it)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't respond to request", e)
+ }
}
}
- }
- override fun cancelRequestAddTile(packageName: String) {
- dialogCanceller?.invoke(packageName)
+ override fun cancelRequestAddTile(packageName: String) {
+ dialogCanceller?.invoke(packageName)
+ }
}
- }
fun init() {
commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() }
@@ -100,58 +102,87 @@ class TileServiceRequestController(
}
@VisibleForTesting
- internal fun requestTileAdd(
+ fun requestTileAdd(
callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
icon: Icon?,
- callback: Consumer<Int>
- ) {
+ callback: Consumer<Int>,
+ ): SystemUIDialog? {
val instanceId = eventLogger.newInstanceId()
val packageName = componentName.packageName
if (isTileAlreadyAdded(componentName)) {
callback.accept(TILE_ALREADY_ADDED)
eventLogger.logTileAlreadyAdded(packageName, instanceId)
- return
+ return null
}
- val dialogResponse = SingleShotConsumer<Int> { response ->
- if (response == ADD_TILE) {
- addTile(componentName)
- }
- dialogCanceller = null
- eventLogger.logUserResponse(response, packageName, instanceId)
- callback.accept(response)
- }
- val tileData = TileRequestDialog.TileData(
- callingUid,
- appName,
- label,
- icon,
- componentName.packageName,
- )
- createDialog(tileData, dialogResponse).also { dialog ->
- dialogCanceller = {
- if (packageName == it) {
- dialog.cancel()
+ val dialogResponse =
+ SingleShotConsumer<Int> { response ->
+ if (response == ADD_TILE) {
+ addTile(componentName)
}
dialogCanceller = null
+ eventLogger.logUserResponse(response, packageName, instanceId)
+ callback.accept(response)
+ }
+ val tileData = TileData(callingUid, appName, label, icon, componentName.packageName)
+ return if (QsInCompose.isEnabled) {
+ createComposeDialog(tileData, dialogResponse)
+ } else {
+ createDialog(tileData, dialogResponse)
+ }
+ .also { dialog ->
+ dialogCanceller = {
+ if (packageName == it) {
+ dialog.cancel()
+ }
+ dialogCanceller = null
+ }
+ dialog.show()
+ eventLogger.logDialogShown(packageName, instanceId)
+ }
+ }
+
+ private fun createComposeDialog(
+ tileData: TileData,
+ responseHandler: SingleShotConsumer<Int>,
+ ): SystemUIDialog {
+ val dialogClickListener =
+ DialogInterface.OnClickListener { _, which ->
+ if (which == Dialog.BUTTON_POSITIVE) {
+ responseHandler.accept(ADD_TILE)
+ } else {
+ responseHandler.accept(DONT_ADD_TILE)
+ }
+ }
+ return tileRequestDialogComposeDelegateFactory
+ .create(dialogListener = dialogClickListener, tiledata = tileData)
+ .createDialog()
+ .apply {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setOnCancelListener { responseHandler.accept(DISMISSED) }
+ // We want this in case the dialog is dismissed without it being cancelled (for
+ // example
+ // by going home or locking the device). We use a SingleShotConsumer so the response
+ // is only sent once, with the first value.
+ setOnDismissListener { responseHandler.accept(DISMISSED) }
}
- }.show()
- eventLogger.logDialogShown(packageName, instanceId)
}
private fun createDialog(
- tileData: TileRequestDialog.TileData,
- responseHandler: SingleShotConsumer<Int>
+ tileData: TileData,
+ responseHandler: SingleShotConsumer<Int>,
): SystemUIDialog {
- val dialogClickListener = DialogInterface.OnClickListener { _, which ->
- if (which == Dialog.BUTTON_POSITIVE) {
- responseHandler.accept(ADD_TILE)
- } else {
- responseHandler.accept(DONT_ADD_TILE)
+ val dialogClickListener =
+ DialogInterface.OnClickListener { _, which ->
+ if (which == Dialog.BUTTON_POSITIVE) {
+ responseHandler.accept(ADD_TILE)
+ } else {
+ responseHandler.accept(DONT_ADD_TILE)
+ }
}
- }
return dialogCreator().apply {
setTileData(tileData, iUriGrantsManager)
setShowForAllUsers(true)
@@ -173,19 +204,20 @@ class TileServiceRequestController(
inner class TileServiceRequestCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
- val componentName: ComponentName = ComponentName.unflattenFromString(args[0])
+ val componentName: ComponentName =
+ ComponentName.unflattenFromString(args[0])
?: run {
Log.w(TAG, "Malformed componentName ${args[0]}")
return
}
- requestTileAdd(0, componentName, args[1], args[2], null) {
- Log.d(TAG, "Response: $it")
- }
+ requestTileAdd(0, componentName, args[1], args[2], null) { Log.d(TAG, "Response: $it") }
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar tile-service-add " +
- "<componentName> <appName> <label>")
+ pw.println(
+ "Usage: adb shell cmd statusbar tile-service-add " +
+ "<componentName> <appName> <label>"
+ )
}
}
@@ -200,18 +232,23 @@ class TileServiceRequestController(
}
@SysUISingleton
- class Builder @Inject constructor(
+ class Builder
+ @Inject
+ constructor(
private val commandQueue: CommandQueue,
private val commandRegistry: CommandRegistry,
private val iUriGrantsManager: IUriGrantsManager,
+ private val tileRequestDialogComposeDelegateFactory:
+ TileRequestDialogComposeDelegate.Factory,
) {
fun create(qsHost: QSHost): TileServiceRequestController {
return TileServiceRequestController(
- qsHost,
- commandQueue,
- commandRegistry,
- TileRequestDialogEventLogger(),
- iUriGrantsManager,
+ qsHost,
+ commandQueue,
+ commandRegistry,
+ TileRequestDialogEventLogger(),
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
)
}
}
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
new file mode 100644
index 000000000000..446be9b9ebcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.dialog
+
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.DialogInterface.OnClickListener
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.dialog.ui.composable.AlertDialogContent
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.TileRequestDialogViewModel
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.LargeStaticTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class TileRequestDialogComposeDelegate
+@AssistedInject
+constructor(
+ private val sysuiDialogFactory: SystemUIDialogFactory,
+ private val tileRequestDialogViewModelFactory: TileRequestDialogViewModel.Factory,
+ @Assisted private val tileData: TileData,
+ @Assisted private val dialogListener: OnClickListener,
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog {
+ return sysuiDialogFactory.create { TileRequestDialogContent(it) }
+ }
+
+ @Composable
+ private fun TileRequestDialogContent(dialog: SystemUIDialog) {
+ PlatformTheme {
+ AlertDialogContent(
+ title = {},
+ content = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = spacedBy(16.dp),
+ ) {
+ val viewModel =
+ rememberViewModel(traceName = "TileRequestDialog", key = tileData) {
+ tileRequestDialogViewModelFactory.create(dialog.context, tileData)
+ }
+
+ Text(
+ text =
+ stringResource(
+ R.string.qs_tile_request_dialog_text,
+ tileData.appName,
+ ),
+ textAlign = TextAlign.Start,
+ )
+
+ LargeStaticTile(
+ uiState = viewModel.uiState,
+ modifier =
+ Modifier.width(
+ dimensionResource(
+ id = R.dimen.qs_tile_service_request_tile_width
+ )
+ ),
+ )
+ }
+ },
+ positiveButton = {
+ PlatformButton(
+ onClick = {
+ dialogListener.onClick(dialog, BUTTON_POSITIVE)
+ dialog.dismiss()
+ }
+ ) {
+ Text(stringResource(R.string.qs_tile_request_dialog_add))
+ }
+ },
+ negativeButton = {
+ PlatformOutlinedButton(
+ onClick = {
+ dialogListener.onClick(dialog, BUTTON_NEGATIVE)
+ dialog.dismiss()
+ }
+ ) {
+ Text(stringResource(R.string.qs_tile_request_dialog_not_add))
+ }
+ },
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ tiledata: TileData,
+ dialogListener: OnClickListener,
+ ): TileRequestDialogComposeDelegate
+ }
+}
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
new file mode 100644
index 000000000000..c756adc07ba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.viewmodel
+
+import android.app.IUriGrantsManager
+import android.content.Context
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+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.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.withContext
+
+class TileRequestDialogViewModel
+@AssistedInject
+constructor(
+ private val iUriGrantsManager: IUriGrantsManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Assisted private val dialogContext: Context,
+ @Assisted private val tileData: TileData,
+) : ExclusiveActivatable() {
+
+ private var _icon by mutableStateOf(defaultIcon)
+
+ private val state: QSTile.State
+ get() =
+ QSTile.State().apply {
+ label = tileData.label
+ handlesLongClick = false
+ this.icon = _icon
+ }
+
+ val uiState by derivedStateOf { state.toUiState(dialogContext.resources) }
+
+ override suspend fun onActivated(): Nothing {
+ withContext(backgroundDispatcher) {
+ tileData.icon
+ ?.loadDrawableCheckingUriGrant(
+ dialogContext,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )
+ ?.run { _icon = DrawableIcon(this) }
+ }
+ awaitCancellation()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(dialogContext: Context, tileData: TileData): TileRequestDialogViewModel
+ }
+
+ companion object {
+ private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt
new file mode 100644
index 000000000000..3067ccbb7cea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.flags
+
+import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.shade.shared.flag.DualShade
+
+/**
+ * Object to help check if the new QS ui should be used. This is true if either [QSComposeFragment]
+ * or [DualShade] are enabled.
+ */
+object QsInCompose {
+
+ /**
+ * This is not a real flag name, but a representation of the allowed flag names. Should not be
+ * used with test annotations.
+ */
+ private val flagName = "${QSComposeFragment.FLAG_NAME}|${DualShade.FLAG_NAME}"
+
+ @JvmStatic
+ inline val isEnabled: Boolean
+ get() = QSComposeFragment.isEnabled || DualShade.isEnabled
+
+ @JvmStatic
+ fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
+
+ @JvmStatic fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
+}
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 8ef637545e69..cc872060b827 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
@@ -23,12 +23,12 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -38,6 +38,7 @@ import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.util.icuMessageFormat
import javax.inject.Inject
import javax.inject.Named
@@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
-import com.android.app.tracing.coroutines.launchTraced as launch
private const val TAG = "FooterActionsViewModel"
@@ -113,7 +113,7 @@ class FooterActionsViewModel(
class Factory
@Inject
constructor(
- @ShadeDisplayAware private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val falsingManager: FalsingManager,
private val footerActionsInteractor: FooterActionsInteractor,
private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
@@ -211,7 +211,7 @@ fun FooterActionsViewModel(
false /* if the dismiss should be deferred */
},
null /* cancelAction */,
- true /* afterKeyguardGone */
+ true, /* afterKeyguardGone */
)
}
@@ -269,29 +269,7 @@ fun FooterActionsViewModel(
.distinctUntilChanged()
val userSwitcher =
- footerActionsInteractor.userSwitcherStatus
- .map { userSwitcherStatus ->
- when (userSwitcherStatus) {
- UserSwitcherStatusModel.Disabled -> null
- is UserSwitcherStatusModel.Enabled -> {
- if (userSwitcherStatus.currentUserImage == null) {
- Log.e(
- TAG,
- "Skipped the addition of user switcher button because " +
- "currentUserImage is missing",
- )
- return@map null
- }
-
- userSwitcherButtonViewModel(
- qsThemedContext,
- userSwitcherStatus,
- ::onUserSwitcherClicked
- )
- }
- }
- }
- .distinctUntilChanged()
+ userSwitcherViewModel(qsThemedContext, footerActionsInteractor, ::onUserSwitcherClicked)
val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
val power =
@@ -311,6 +289,36 @@ fun FooterActionsViewModel(
)
}
+fun userSwitcherViewModel(
+ themedContext: Context,
+ footerActionsInteractor: FooterActionsInteractor,
+ onUserSwitcherClicked: (Expandable) -> Unit,
+): Flow<FooterActionsButtonViewModel?> {
+ return footerActionsInteractor.userSwitcherStatus
+ .map { userSwitcherStatus ->
+ when (userSwitcherStatus) {
+ UserSwitcherStatusModel.Disabled -> null
+ is UserSwitcherStatusModel.Enabled -> {
+ if (userSwitcherStatus.currentUserImage == null) {
+ Log.e(
+ TAG,
+ "Skipped the addition of user switcher button because " +
+ "currentUserImage is missing",
+ )
+ return@map null
+ }
+
+ userSwitcherButtonViewModel(
+ themedContext,
+ userSwitcherStatus,
+ onUserSwitcherClicked,
+ )
+ }
+ }
+ }
+ .distinctUntilChanged()
+}
+
fun securityButtonViewModel(
config: SecurityButtonConfig,
onSecurityButtonClicked: (Context, Expandable) -> Unit,
@@ -369,7 +377,7 @@ fun userSwitcherButtonViewModel(
private fun userSwitcherContentDescription(
qsThemedContext: Context,
- currentUser: String?
+ currentUser: String?,
): String? {
return currentUser?.let { user ->
qsThemedContext.getString(R.string.accessibility_quick_settings_user, user)
@@ -384,13 +392,9 @@ fun settingsButtonViewModel(
id = R.id.settings_button_container,
Icon.Resource(
R.drawable.ic_settings,
- ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
+ ContentDescription.Resource(R.string.accessibility_quick_settings_settings),
),
- iconTint =
- Utils.getColorAttrDefaultColor(
- qsThemedContext,
- R.attr.onShadeInactiveVariant,
- ),
+ iconTint = Utils.getColorAttrDefaultColor(qsThemedContext, R.attr.onShadeInactiveVariant),
backgroundColor = R.attr.shadeInactive,
onSettingsButtonClicked,
)
@@ -404,14 +408,14 @@ fun powerButtonViewModel(
id = R.id.pm_lite,
Icon.Resource(
android.R.drawable.ic_lock_power_off,
- ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
),
iconTint =
Utils.getColorAttrDefaultColor(
qsThemedContext,
- R.attr.onShadeActive,
+ if (DualShade.isEnabled) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
),
- backgroundColor = R.attr.shadeActive,
+ backgroundColor = if (DualShade.isEnabled) R.attr.shadeInactive else R.attr.shadeActive,
onPowerButtonClicked,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 1f55ac777de5..f4bf53cafd19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -21,8 +21,6 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor
import com.android.systemui.qs.panels.domain.interactor.SizedTilesResetInteractor
import com.android.systemui.qs.panels.shared.model.GridLayoutType
@@ -49,9 +47,6 @@ interface PanelsModule {
): DefaultLargeTilesRepository
@Binds
- fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
-
- @Binds
fun bindEditTilesResetInteractor(impl: SizedTilesResetInteractor): EditTilesResetInteractor
@Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 47c4ffd6a2cc..f17abe888b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -17,28 +17,14 @@
package com.android.systemui.qs.panels.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-interface GridLayoutTypeRepository {
- val layout: StateFlow<GridLayoutType>
-
- fun setLayout(type: GridLayoutType)
-}
+import kotlinx.coroutines.flow.flowOf
@SysUISingleton
-class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
- private val _layout: MutableStateFlow<GridLayoutType> =
- MutableStateFlow(PaginatedGridLayoutType)
- override val layout = _layout.asStateFlow()
+class GridLayoutTypeRepository @Inject constructor() {
+ val defaultLayoutType = flowOf(PaginatedGridLayoutType)
- override fun setLayout(type: GridLayoutType) {
- if (_layout.value != type) {
- _layout.value = type
- }
- }
+ val dualShadeLayoutType = flowOf(InfiniteGridLayoutType)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
index 58834037e2b7..eeec9b3ef5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
@@ -20,8 +20,8 @@ import android.content.res.Resources
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -38,8 +38,8 @@ class LargeTileSpanRepository
@Inject
constructor(
@Application scope: CoroutineScope,
- @Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val resources: Resources,
+ @ShadeDisplayAware configurationRepository: ConfigurationRepository,
) {
val span: StateFlow<Int> =
configurationRepository.onConfigurationChange
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
index 424be90ba2ec..6746efac4aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
@@ -19,8 +19,8 @@ package com.android.systemui.qs.panels.data.repository
import android.content.res.Resources
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.flow.map
@@ -33,8 +33,8 @@ import kotlinx.coroutines.flow.map
class PaginatedGridRepository
@Inject
constructor(
- @Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val resources: Resources,
+ @ShadeDisplayAware configurationRepository: ConfigurationRepository,
) {
val rows =
configurationRepository.onConfigurationChange.emitOnStart().map {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
index a9205c27216d..693681d090d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
@@ -19,8 +19,8 @@ package com.android.systemui.qs.panels.data.repository
import android.content.res.Resources
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,8 +33,8 @@ import kotlinx.coroutines.flow.mapLatest
class QSColumnsRepository
@Inject
constructor(
- @Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val resources: Resources,
+ @ShadeDisplayAware configurationRepository: ConfigurationRepository,
) {
val splitShadeColumns: Flow<Int> =
flowOf(resources.getInteger(R.integer.quick_settings_split_shade_num_columns))
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
index ee0cfb304db0..636f703ac65a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
@@ -19,8 +19,8 @@ package com.android.systemui.qs.panels.data.repository
import android.content.res.Resources
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.flow.map
@@ -29,8 +29,8 @@ import kotlinx.coroutines.flow.map
class QuickQuickSettingsRowRepository
@Inject
constructor(
- @Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val resources: Resources,
+ @ShadeDisplayAware configurationRepository: ConfigurationRepository,
) {
val rows =
configurationRepository.onConfigurationChange.emitOnStart().map {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
index 86a29f91e51c..a2d892c86f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt
@@ -19,17 +19,15 @@ package com.android.systemui.qs.panels.data.repository
import android.content.res.Resources
import com.android.server.display.feature.flags.Flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
@SysUISingleton
class StockTilesRepository
@Inject
-constructor(
- @Main private val resources: Resources,
-) {
+constructor(@ShadeDisplayAware private val resources: Resources) {
/**
* List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec].
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index 4af1b2223c4c..e493cbeebae8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -19,14 +19,23 @@ package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
@SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
- val layout: StateFlow<GridLayoutType> = repo.layout
-
- fun setLayoutType(type: GridLayoutType) {
- repo.setLayout(type)
- }
+@OptIn(ExperimentalCoroutinesApi::class)
+class GridLayoutTypeInteractor
+@Inject
+constructor(private val repo: GridLayoutTypeRepository, shadeModeInteractor: ShadeModeInteractor) {
+ val layout: Flow<GridLayoutType> =
+ shadeModeInteractor.shadeMode.flatMapLatest { shadeMode ->
+ when (shadeMode) {
+ is ShadeMode.Dual -> repo.dualShadeLayoutType
+ else -> repo.defaultLayoutType
+ }
+ }
}
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 b6dbf4db57a4..39408d3dee72 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
@@ -49,9 +49,10 @@ import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight
import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
+import com.android.systemui.qs.panels.ui.compose.toolbar.EditModeButton
import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
import com.android.systemui.qs.ui.compose.borderOnFocus
import javax.inject.Inject
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 dbad60265645..d72d5f127bba 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
@@ -21,7 +21,6 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
@@ -192,7 +191,6 @@ fun LargeTileLabels(
}
}
-@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
fun SmallTileContent(
modifier: Modifier = Modifier,
@@ -229,6 +227,7 @@ fun SmallTileContent(
}
}
}
+
is Icon.Loaded -> {
LaunchedEffect(loadedDrawable) {
if (loadedDrawable is AnimatedVectorDrawable) {
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 c6141a1a7cc2..a05747dd3ba2 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
@@ -28,6 +28,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
@@ -138,7 +139,6 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.groupAndSort
import com.android.systemui.res.R
-import kotlin.math.max
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -148,8 +148,9 @@ object TileType
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
+
TopAppBar(
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Black),
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
title = { Text(text = stringResource(id = R.string.qs_edit)) },
navigationIcon = {
IconButton(onClick = onStopEditing) {
@@ -209,7 +210,15 @@ fun DefaultEditTileGrid(
Column(
verticalArrangement =
spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize().verticalScroll(scrollState).padding(innerPadding),
+ modifier =
+ modifier
+ .fillMaxSize()
+ // Apply top padding before the scroll so the scrollable doesn't show under
+ // the
+ // top bar
+ .padding(top = innerPadding.calculateTopPadding())
+ .clipScrollableContainer(Orientation.Vertical)
+ .verticalScroll(scrollState),
) {
AnimatedContent(
targetState = listState.dragInProgress,
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 abdf923ebe73..13b331163d44 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
@@ -24,6 +24,7 @@ import android.service.quicksettings.Tile.STATE_INACTIVE
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
@@ -125,7 +126,7 @@ fun Tile(
modifier: Modifier = Modifier,
detailsViewModel: DetailsViewModel?,
) {
- val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val state: QSTile.State by tile.state.collectAsStateWithLifecycle(tile.currentState)
val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
@@ -263,6 +264,28 @@ fun TileContainer(
}
@Composable
+fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
+ val colors = TileDefaults.getColorForState(uiState = uiState, iconOnly = false)
+
+ Box(
+ modifier
+ .clip(TileDefaults.animateTileShape(state = uiState.state))
+ .background(colors.background)
+ .height(TileHeight)
+ .tilePadding()
+ ) {
+ LargeTileContent(
+ label = uiState.label,
+ secondaryLabel = "",
+ icon = getTileIcon(icon = uiState.icon),
+ sideDrawable = null,
+ colors = colors,
+ squishiness = { 1f },
+ )
+ }
+}
+
+@Composable
private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
val context = LocalContext.current
return icon.get()?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
index c2764f9f338b..85db95203b45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.compose
+package com.android.systemui.qs.panels.ui.compose.toolbar
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -30,7 +30,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
import com.android.systemui.qs.ui.compose.borderOnFocus
import com.android.systemui.res.R
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
new file mode 100644
index 000000000000..37fa9e799521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose.toolbar
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+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.lifecycle.rememberViewModel
+import com.android.systemui.qs.footer.ui.compose.IconButton
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
+
+@Composable
+fun Toolbar(toolbarViewModelFactory: ToolbarViewModel.Factory, modifier: Modifier = Modifier) {
+ val viewModel = rememberViewModel("Toolbar") { toolbarViewModelFactory.create() }
+
+ Row(
+ modifier = modifier.fillMaxWidth().requiredHeight(48.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ viewModel.userSwitcherViewModel?.let {
+ IconButton(it, Modifier.sysuiResTag("multi_user_switch"))
+ }
+
+ EditModeButton(viewModel.editModeButtonViewModelFactory)
+
+ IconButton(
+ viewModel.settingsButtonViewModel,
+ Modifier.sysuiResTag("settings_button_container"),
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+ IconButton(viewModel.powerButtonViewModel, Modifier.sysuiResTag("pm_lite"))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 4a18872ad6f6..3fcb2ab37b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 44dd801a8b9f..9462321ad9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onStart
@Immutable
@@ -37,6 +38,7 @@ class TileViewModel(private val tile: QSTile, val spec: TileSpec) {
awaitClose { tile.removeCallback(callback) }
}
.onStart { emit(tile.state) }
+ .filterNotNull()
.distinctUntilChanged()
val currentState: QSTile.State
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
index b033473a91e5..f60621882ac0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
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
new file mode 100644
index 000000000000..0fde855f576f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
+
+import android.content.Context
+import android.view.ContextThemeWrapper
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+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.globalactions.GlobalActionsDialogLite
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.powerButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.settingsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.userSwitcherViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Provider
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class ToolbarViewModel
+@AssistedInject
+constructor(
+ val editModeButtonViewModelFactory: EditModeButtonViewModel.Factory,
+ private val footerActionsInteractor: FooterActionsInteractor,
+ private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
+ private val falsingInteractor: FalsingInteractor,
+ @ShadeDisplayAware appContext: Context,
+) : ExclusiveActivatable() {
+ private val qsThemedContext =
+ ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+ private val hydrator = Hydrator("ToolbarViewModel.hydrator")
+
+ val powerButtonViewModel = powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked)
+
+ val settingsButtonViewModel =
+ settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
+
+ val userSwitcherViewModel: FooterActionsButtonViewModel? by
+ hydrator.hydratedStateOf(
+ traceName = "userSwitcherViewModel",
+ initialValue = null,
+ source =
+ userSwitcherViewModel(
+ qsThemedContext,
+ footerActionsInteractor,
+ ::onUserSwitcherClicked,
+ ),
+ )
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ try {
+ globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+ awaitCancellation()
+ } finally {
+ globalActionsDialogLite?.destroy()
+ }
+ }
+ launch { hydrator.activate() }
+ awaitCancellation()
+ }
+ }
+
+ private var globalActionsDialogLite: GlobalActionsDialogLite? by mutableStateOf(null)
+
+ private fun onPowerButtonClicked(expandable: Expandable) {
+ falsingInteractor.runIfNotFalseTap {
+ globalActionsDialogLite?.let {
+ footerActionsInteractor.showPowerMenuDialog(it, expandable)
+ }
+ }
+ }
+
+ private fun onUserSwitcherClicked(expandable: Expandable) {
+ falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showUserSwitcher(expandable) }
+ }
+
+ private fun onSettingsButtonClicked(expandable: Expandable) {
+ falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showSettings(expandable) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): ToolbarViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
index fe0a69b03287..d4ac9013cf58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
@@ -2,9 +2,9 @@ package com.android.systemui.qs.pipeline.data.repository
import android.content.res.Resources
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
interface DefaultTilesRepository {
@@ -14,9 +14,7 @@ interface DefaultTilesRepository {
@SysUISingleton
class DefaultTilesQSHostRepository
@Inject
-constructor(
- @Main private val resources: Resources,
-) : DefaultTilesRepository {
+constructor(@ShadeDisplayAware private val resources: Resources) : DefaultTilesRepository {
override val defaultTiles: List<TileSpec>
get() =
QSHost.getDefaultSpecs(resources).map(TileSpec::create).filter {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
index 3a005c0ebfed..40720a28ce09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
@@ -18,8 +18,8 @@ package com.android.systemui.qs.pipeline.data.repository
import android.content.res.Resources
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
@@ -35,7 +35,7 @@ interface MinimumTilesRepository {
* creation, as it's not expected to change.
*/
@SysUISingleton
-class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :
+class MinimumTilesResourceRepository @Inject constructor(@ShadeDisplayAware resources: Resources) :
MinimumTilesRepository {
override val minNumberOfTiles: Int =
resources.getInteger(R.integer.quick_settings_min_num_tiles)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index d94e7cfab5f1..c6751b7717e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,12 +20,12 @@ import android.annotation.UserIdInt
import android.content.res.Resources
import android.util.SparseArray
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -92,7 +92,7 @@ interface TileSpecRepository {
class TileSpecSettingsRepository
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val logger: QSPipelineLogger,
private val retailModeRepository: RetailModeRepository,
private val userTileSpecRepositoryFactory: UserTileSpecRepository.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 9abc494e56e6..1d1e9911884c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,6 +31,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.CallSuper;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -68,6 +69,7 @@ import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SideLabelTileLayout;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import java.io.PrintWriter;
@@ -367,6 +369,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mHandler.sendEmptyMessage(H.INITIALIZE);
}
+ @androidx.annotation.NonNull
public TState getState() {
return mState;
}
@@ -535,6 +538,23 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
}
}
+ protected Icon maybeLoadResourceIcon(int id) {
+ return maybeLoadResourceIcon(id, mContext);
+ }
+
+ /**
+ * Returns the {@link QSTile.Icon} for the resource ID, optionally loading the drawable if
+ * {@link QsInCompose#isEnabled()} is true.
+ */
+ @SuppressLint("UseCompatLoadingForDrawables")
+ public static Icon maybeLoadResourceIcon(int id, Context context) {
+ if (QsInCompose.isEnabled()) {
+ return new DrawableIconWithRes(context.getDrawable(id), id);
+ } else {
+ return ResourceIcon.get(id);
+ }
+ }
+
@Override
public String getMetricsSpec() {
return mTileSpec;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 71b69c92b87d..bb818fa5e164 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -160,7 +160,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.label = mContext.getString(R.string.airplane_mode);
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_airplane_icon_on : R.drawable.qs_airplane_icon_off);
state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 73d991f6efe7..9efdd98df4cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -9,6 +9,7 @@ import android.provider.AlarmClock
import android.service.quicksettings.Tile
import android.text.TextUtils
import android.text.format.DateFormat
+import android.widget.Button
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
@@ -42,26 +43,28 @@ constructor(
activityStarter: ActivityStarter,
qsLogger: QSLogger,
private val userTracker: UserTracker,
- nextAlarmController: NextAlarmController
-) : QSTileImpl<QSTile.State>(
- host,
- uiEventLogger,
- backgroundLooper,
- mainHandler,
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger
-) {
+ nextAlarmController: NextAlarmController,
+) :
+ QSTileImpl<QSTile.State>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ ) {
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
- private val icon = ResourceIcon.get(R.drawable.ic_alarm)
+ private var icon: QSTile.Icon? = null
@VisibleForTesting internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
- private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
- lastAlarmInfo = nextAlarm
- refreshState()
- }
+ private val callback =
+ NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
+ lastAlarmInfo = nextAlarm
+ refreshState()
+ }
init {
nextAlarmController.observe(this, callback)
@@ -70,6 +73,7 @@ constructor(
override fun newTileState(): QSTile.State {
return QSTile.State().apply {
handlesLongClick = false
+ expandedAccessibilityClassName = Button::class.java.name
}
}
@@ -82,21 +86,28 @@ constructor(
if (pendingIntent != null) {
mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
} else {
- mActivityStarter.postStartActivityDismissingKeyguard(defaultIntent, 0,
- animationController)
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ defaultIntent,
+ 0,
+ animationController,
+ )
}
}
override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+ if (icon == null) {
+ icon = maybeLoadResourceIcon(R.drawable.ic_alarm)
+ }
state.icon = icon
state.label = tileLabel
lastAlarmInfo?.let {
state.secondaryLabel = formatNextAlarm(it)
state.state = Tile.STATE_ACTIVE
- } ?: run {
- state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
- state.state = Tile.STATE_INACTIVE
}
+ ?: run {
+ state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
+ state.state = Tile.STATE_INACTIVE
+ }
state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 7c0ce4cc75a9..9df4e42d1898 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -147,9 +147,8 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
protected void handleUpdateState(BooleanState state, Object arg) {
state.state = mPluggedIn ? Tile.STATE_UNAVAILABLE
: mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(mPowerSave
- ? R.drawable.qs_battery_saver_icon_on
- : R.drawable.qs_battery_saver_icon_off);
+ state.icon = maybeLoadResourceIcon(mPowerSave
+ ? R.drawable.qs_battery_saver_icon_on : R.drawable.qs_battery_saver_icon_off);
state.label = mContext.getString(R.string.battery_detail_switch_title);
state.secondaryLabel = "";
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7bff827dee03..7eb0aaabb7e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -59,13 +59,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
+import kotlinx.coroutines.Job;
+
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import kotlinx.coroutines.Job;
-
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTileImpl<BooleanState> {
@@ -201,7 +201,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
if (enabled) {
if (connected) {
- state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_on);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_on);
if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
state.label = mController.getConnectedDeviceName();
}
@@ -209,17 +209,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
mContext.getString(R.string.accessibility_bluetooth_name, state.label)
+ ", " + state.secondaryLabel;
} else if (state.isTransient) {
- state.icon = ResourceIcon.get(
- R.drawable.qs_bluetooth_icon_search);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_search);
state.stateDescription = state.secondaryLabel;
} else {
- state.icon =
- ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_off);
state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
}
state.state = Tile.STATE_ACTIVE;
} else {
- state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_off);
state.state = Tile.STATE_INACTIVE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 8a72e8db7216..30c2adf89e9b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Intent;
import android.media.MediaRouter.RouteInfo;
+import android.media.projection.StopReason;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
@@ -183,7 +184,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
});
}
} else {
- mController.stopCasting(activeDevices.get(0));
+ mController.stopCasting(activeDevices.get(0), StopReason.STOP_QS_TILE);
}
}
@@ -290,8 +291,8 @@ public class CastTile extends QSTileImpl<BooleanState> {
if (connecting && !state.value) {
state.secondaryLabel = mContext.getString(R.string.quick_settings_connecting);
}
- state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
- : R.drawable.ic_cast);
+ state.icon = maybeLoadResourceIcon(state.value
+ ? R.drawable.ic_cast_connected : R.drawable.ic_cast);
if (canCastToNetwork() || state.value) {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
if (!state.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index 871973dfcb7f..c2e609ddfc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -50,7 +50,8 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> {
public static final String TILE_SPEC = "color_correction";
- private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction);
+ @Nullable
+ private Icon mIcon = null;
private final UserSettingObserver mSetting;
@Inject
@@ -122,6 +123,9 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
+ if (mIcon == null) {
+ mIcon = maybeLoadResourceIcon(R.drawable.ic_qs_color_correction);
+ }
state.value = enabled;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.quick_settings_color_correction_label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 58969107ad22..ce80133e67a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -124,7 +124,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
state.value = enabled;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_invert_colors_icon_on
: R.drawable.qs_invert_colors_icon_off);
state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7760943476bf..deeef550b33f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -147,7 +147,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.data_saver);
state.contentDescription = state.label;
- state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on
+ state.icon = maybeLoadResourceIcon(state.value ? R.drawable.qs_data_saver_icon_on
: R.drawable.qs_data_saver_icon_off);
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index cc8a73423174..7213f7a60da0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -1,4 +1,3 @@
-
/*
* Copyright (C) 2021 The Android Open Source Project
*
@@ -22,10 +21,9 @@ import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.service.quicksettings.Tile
-import androidx.annotation.VisibleForTesting
+import android.widget.Button
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
@@ -43,10 +41,13 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-class DeviceControlsTile @Inject constructor(
+class DeviceControlsTile
+@Inject
+constructor(
host: QSHost,
uiEventLogger: QsEventLogger,
@Background backgroundLooper: Looper,
@@ -56,32 +57,34 @@ class DeviceControlsTile @Inject constructor(
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
qsLogger: QSLogger,
- private val controlsComponent: ControlsComponent
-) : QSTileImpl<QSTile.State>(
- host,
- uiEventLogger,
- backgroundLooper,
- mainHandler,
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger
-) {
+ private val controlsComponent: ControlsComponent,
+) :
+ QSTileImpl<QSTile.State>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ ) {
private var hasControlsApps = AtomicBoolean(false)
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- val icon: QSTile.Icon
- get() = ResourceIcon.get(controlsComponent.getTileImageId())
+ private var icon: QSTile.Icon? = null
- private val listingCallback = object : ControlsListingController.ControlsListingCallback {
- override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
- if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
- refreshState()
+ private val listingCallback =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ if (
+ hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())
+ ) {
+ refreshState()
+ }
}
}
- }
init {
controlsComponent.getControlsListingController().ifPresent {
@@ -105,15 +108,19 @@ class DeviceControlsTile @Inject constructor(
return
}
- val intent = Intent().apply {
- component = ComponentName(mContext, controlsComponent.getControlsUiController().get()
- .resolveActivity())
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- }
+ val intent =
+ Intent().apply {
+ component =
+ ComponentName(
+ mContext,
+ controlsComponent.getControlsUiController().get().resolveActivity(),
+ )
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ }
val animationController =
expandable?.activityTransitionController(
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
)
mUiHandler.post {
@@ -130,17 +137,23 @@ class DeviceControlsTile @Inject constructor(
override fun handleUpdateState(state: QSTile.State, arg: Any?) {
state.label = tileLabel
state.contentDescription = state.label
+ if (icon == null) {
+ icon = maybeLoadResourceIcon(controlsComponent.getTileImageId())
+ }
state.icon = icon
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
if (controlsComponent.getVisibility() == AVAILABLE) {
- val selection = controlsComponent
- .getControlsController().get().getPreferredSelection()
- state.state = if (selection is SelectedItem.StructureItem &&
- selection.structure.controls.isEmpty()) {
- Tile.STATE_INACTIVE
- } else {
- Tile.STATE_ACTIVE
- }
+ val selection =
+ controlsComponent.getControlsController().get().getPreferredSelection()
+ state.state =
+ if (
+ selection is SelectedItem.StructureItem &&
+ selection.structure.controls.isEmpty()
+ ) {
+ Tile.STATE_INACTIVE
+ } else {
+ Tile.STATE_ACTIVE
+ }
val label = selection.name
state.secondaryLabel = if (label == tileLabel) null else label
} else {
@@ -151,6 +164,7 @@ class DeviceControlsTile @Inject constructor(
} else {
state.state = Tile.STATE_UNAVAILABLE
}
+ state.expandedAccessibilityClassName = Button::class.java.name
}
override fun getMetricsCategory(): Int {
@@ -170,4 +184,4 @@ class DeviceControlsTile @Inject constructor(
companion object {
const val TILE_SPEC = "controls"
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ad76b4f21bfb..04f0b8736598 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -229,7 +229,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
state.dualTarget = true;
state.value = newValue;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_dnd_icon_on
: R.drawable.qs_dnd_icon_off);
state.label = getTileLabel();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 0d3d980f71f4..e37ed16133e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -32,6 +32,7 @@ import android.service.dreams.IDreamManager;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
+import android.widget.Switch;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -64,9 +65,6 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
public static final String TILE_SPEC = "dream";
private static final String LOG_TAG = "QSDream";
- // TODO: consider 1 animated icon instead
- private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver);
- private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
private final IDreamManager mDreamManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final UserSettingObserver mEnabledSettingObserver;
@@ -170,13 +168,16 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
state.label = getTileLabel();
state.secondaryLabel = getActiveDreamName();
state.contentDescription = getContentDescription(state.secondaryLabel);
- state.icon = mIsDocked ? mIconDocked : mIconUndocked;
+ // TODO: consider 1 animated icon instead
+ state.icon = maybeLoadResourceIcon(mIsDocked
+ ? R.drawable.ic_qs_screen_saver : R.drawable.ic_qs_screen_saver_undocked);
if (getActiveDream() == null || !isScreensaverEnabled()) {
state.state = Tile.STATE_UNAVAILABLE;
} else {
state.state = isDreaming() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ state.expandedAccessibilityClassName = Switch.class.getName();
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 848ff3c533ba..2b127d60b2be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -128,7 +128,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
R.string.quick_settings_flashlight_camera_in_use);
state.stateDescription = state.secondaryLabel;
state.state = Tile.STATE_UNAVAILABLE;
- state.icon = ResourceIcon.get(R.drawable.qs_flashlight_icon_off);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_flashlight_icon_off);
return;
}
if (arg instanceof Boolean) {
@@ -143,7 +143,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_flashlight_icon_on : R.drawable.qs_flashlight_icon_off);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 7606293454f8..43e84a0ee2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -68,7 +68,7 @@ constructor(
activityStarter,
qsLogger,
) {
- private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
+ private var icon: QSTile.Icon? = null
override fun newTileState(): QSTile.State {
return QSTile.State()
@@ -108,6 +108,9 @@ constructor(
}
override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+ if (icon == null) {
+ icon = maybeLoadResourceIcon(R.drawable.ic_qs_font_scaling)
+ }
state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
state?.icon = icon
state?.contentDescription = state?.label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
index f723ff264e0c..74563fff8775 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -24,6 +24,7 @@ import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
import android.service.quicksettings.Tile;
+import android.widget.Button;
import androidx.annotation.Nullable;
@@ -106,7 +107,7 @@ public class HearingDevicesTile extends QSTileImpl<BooleanState> {
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
state.label = mContext.getString(R.string.quick_settings_hearing_devices_label);
- state.icon = ResourceIcon.get(R.drawable.qs_hearing_devices_icon);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_hearing_devices_icon);
state.forceExpandIcon = true;
boolean isBonded = mDevicesChecker.isAnyPairedHearingDevice();
@@ -124,6 +125,7 @@ public class HearingDevicesTile extends QSTileImpl<BooleanState> {
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel = "";
}
+ state.expandedAccessibilityClassName = Button.class.getName();
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index ea3993ea88a9..03bbbd7017ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -151,10 +151,10 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
state.isTransient = isTransient;
if (state.isTransient) {
- state.icon = ResourceIcon.get(
+ state.icon = maybeLoadResourceIcon(
R.drawable.qs_hotspot_icon_search);
} else {
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
}
state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 02f6f80d7282..0a5952997893 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -30,7 +30,7 @@ import android.service.quicksettings.Tile;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.Switch;
+import android.widget.Button;
import androidx.annotation.Nullable;
@@ -136,7 +136,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
}
@Override
- public void secondaryClick(@Nullable Expandable expandable) {
+ public void handleSecondaryClick(@Nullable Expandable expandable) {
// TODO(b/358352265): Figure out the correct action for the secondary click
// Toggle Wifi
mWifiStateWorker.setWifiEnabled(!mWifiStateWorker.isWifiEnabled());
@@ -529,10 +529,10 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
if (cb.mAirplaneModeEnabled) {
if (!state.value) {
state.state = Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (!wifiConnected) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
if (cb.mNoNetworksAvailable) {
state.secondaryLabel =
r.getString(R.string.quick_settings_networks_unavailable);
@@ -541,28 +541,28 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
r.getString(R.string.quick_settings_networks_available);
}
} else {
- state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
+ state.icon = maybeLoadResourceIcon(cb.mWifiSignalIconId);
}
} else if (cb.mNoDefaultNetwork) {
if (cb.mNoNetworksAvailable || !cb.mEnabled) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
}
} else if (cb.mIsTransient) {
- state.icon = ResourceIcon.get(
+ state.icon = maybeLoadResourceIcon(
com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
} else if (!state.value) {
state.state = Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
+ state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_DISABLED);
} else if (wifiConnected) {
- state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
+ state.icon = maybeLoadResourceIcon(cb.mWifiSignalIconId);
} else if (wifiNotConnected) {
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
+ state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_NO_NETWORK);
} else {
- state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
+ state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_NO_NETWORK);
}
minimalContentDescription.append(
mContext.getString(R.string.quick_settings_internet_label)).append(",");
@@ -577,7 +577,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
state.contentDescription = minimalContentDescription.toString();
state.dualLabelContentDescription = r.getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
- state.expandedAccessibilityClassName = Switch.class.getName();
+ state.expandedAccessibilityClassName = Button.class.getName();
if (DEBUG) {
Log.d(TAG, "handleUpdateWifiState: " + "BooleanState = " + state.toString());
}
@@ -594,18 +594,18 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
boolean mobileDataEnabled = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
state.value = mobileDataEnabled;
- state.expandedAccessibilityClassName = Switch.class.getName();
+ state.expandedAccessibilityClassName = Button.class.getName();
if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
state.state = Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (cb.mNoDefaultNetwork) {
if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
}
} else {
@@ -637,7 +637,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> {
final Resources r = mContext.getResources();
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
- state.icon = ResourceIcon.get(cb.mEthernetSignalIconId);
+ state.icon = maybeLoadResourceIcon(cb.mEthernetSignalIconId);
state.secondaryLabel = cb.mEthernetContentDescription;
if (DEBUG) {
Log.d(TAG, "handleUpdateEthernetState: " + "BooleanState = " + state.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 7225800e25e3..6d3e5d07c251 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -20,7 +20,7 @@ import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.provider.Settings
-import android.widget.Switch
+import android.widget.Button
import com.android.internal.logging.MetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
@@ -71,7 +71,7 @@ constructor(
metricsLogger,
statusBarStateController,
activityStarter,
- qsLogger
+ qsLogger,
) {
private var model: InternetTileModel = viewModel.tileModel.value
@@ -110,7 +110,7 @@ constructor(
return InternetDetailsViewModel { longClick(null) }
}
- override fun secondaryClick(expandable: Expandable?) {
+ override fun handleSecondaryClick(expandable: Expandable?) {
// TODO(b/358352265): Figure out the correct action for the secondary click
// Toggle wifi
wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled
@@ -118,7 +118,7 @@ constructor(
override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
- state.expandedAccessibilityClassName = Switch::class.java.name
+ state.expandedAccessibilityClassName = Button::class.java.name
model.applyTo(state, mContext)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index cad5c0d12d1d..f35c25f24162 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -122,7 +122,7 @@ public class LocationTile extends QSTileImpl<BooleanState> {
if (state.disabledByPolicy == false) {
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION);
}
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? R.drawable.qs_location_icon_on : R.drawable.qs_location_icon_off);
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index fef5a745c1ca..0051bf5de7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -109,6 +109,11 @@ constructor(
userActionInteractor.handleClick(expandable)
}
+ override fun handleSecondaryClick(expandable: Expandable?) = runBlocking {
+ val model = dataInteractor.getCurrentTileModel()
+ userActionInteractor.handleToggleClick(model)
+ }
+
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
@VisibleForTesting
@@ -120,11 +125,12 @@ constructor(
tileState = tileMapper.map(config, model)
state?.apply {
this.state = tileState.activationState.legacyState
- icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+ icon = tileState.icon?.asQSTileIcon() ?: maybeLoadResourceIcon(ICON_RES_ID)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+ handlesSecondaryClick = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 136eea8331df..683e4e93cf4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -55,7 +55,8 @@ public class NfcTile extends QSTileImpl<BooleanState> {
public static final String TILE_SPEC = "nfc";
private static final String NFC = TILE_SPEC;
- private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc);
+ @Nullable
+ private Icon mIcon = null;
@Nullable
private NfcAdapter mAdapter;
@@ -137,6 +138,10 @@ public class NfcTile extends QSTileImpl<BooleanState> {
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ if (mIcon == null) {
+ mIcon = maybeLoadResourceIcon(R.drawable.ic_qs_nfc);
+ }
+
state.value = getAdapter() != null && getAdapter().isEnabled();
state.state = getAdapter() == null
? Tile.STATE_UNAVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index ac762de6d544..2f5908752111 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -150,7 +150,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements
state.label = mContext.getString(R.string.quick_settings_night_display_label);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on
+ state.icon = maybeLoadResourceIcon(state.value ? R.drawable.qs_nightlight_icon_on
: R.drawable.qs_nightlight_icon_off);
state.secondaryLabel = getSecondaryLabel(state.value);
state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
index 69df0961bf66..989fc0fd6f44 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
@@ -40,14 +40,14 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlinx.coroutines.runBlocking
/** Quick settings tile: Notes */
class NotesTile
-@Inject constructor(
+@Inject
+constructor(
private val host: QSHost,
private val uiEventLogger: QsEventLogger,
- @Background private val backgroundLooper: Looper,
+ @Background private val backgroundLooper: Looper,
@Main private val mainHandler: Handler,
private val falsingManager: FalsingManager,
private val metricsLogger: MetricsLogger,
@@ -74,8 +74,7 @@ class NotesTile
private lateinit var tileState: QSTileState
private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
- override fun getTileLabel(): CharSequence =
- mContext.getString(config.uiConfig.labelRes)
+ override fun getTileLabel(): CharSequence = mContext.getString(config.uiConfig.labelRes)
override fun newTileState(): QSTile.State? {
return QSTile.State().apply { state = Tile.STATE_INACTIVE }
@@ -88,13 +87,12 @@ class NotesTile
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
- val model =
- if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel()
+ val model = if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel()
tileState = tileMapper.map(config, model)
state?.apply {
this.state = tileState.activationState.legacyState
- icon = ResourceIcon.get(tileState.iconRes ?: R.drawable.ic_qs_notes)
+ icon = maybeLoadResourceIcon(tileState.iconRes ?: R.drawable.ic_qs_notes)
label = tileState.label
contentDescription = tileState.contentDescription
expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 450c95411c3f..c605ac8d80b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -51,8 +51,8 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> {
public static final String TILE_SPEC = "onehanded";
- private final Icon mIcon = ResourceIcon.get(
- com.android.internal.R.drawable.ic_qs_one_handed_mode);
+ @Nullable
+ private Icon mIcon = null;
private final UserSettingObserver mSetting;
@Inject
@@ -125,6 +125,10 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> {
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ if (mIcon == null) {
+ mIcon = maybeLoadResourceIcon(com.android.internal.R.drawable.ic_qs_one_handed_mode);
+ }
+
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
state.value = enabled;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 9766fac7965e..467233d5f71f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -21,6 +21,7 @@ import android.os.Handler;
import android.os.Looper;
import android.service.quicksettings.Tile;
import android.util.Log;
+import android.widget.Button;
import androidx.annotation.Nullable;
@@ -119,13 +120,14 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> {
protected void handleUpdateState(State state, Object arg) {
state.label = mContext.getString(R.string.qr_code_scanner_title);
state.contentDescription = state.label;
- state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
+ state.icon = maybeLoadResourceIcon(R.drawable.ic_qr_code_scanner);
state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE
: Tile.STATE_UNAVAILABLE;
// The assumption is that if the OEM has the QR code scanner module enabled then the scanner
// would go to "Unavailable" state only when GMS core is updating.
state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
+ state.expandedAccessibilityClassName = Button.class.getName();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 37d24debe958..6deb19257591 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -40,6 +40,7 @@ import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quickaccesswallet.WalletCard;
import android.service.quicksettings.Tile;
import android.util.Log;
+import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -142,8 +143,16 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
mUiHandler.post(
- () -> mController.startQuickAccessUiIntent(
- mActivityStarter, animationController, mSelectedCard != null));
+ () -> {
+ if (android.service.quickaccesswallet.Flags.launchSelectedCardFromQsTile()
+ && mSelectedCard != null) {
+ mController.startWalletCardPendingIntent(
+ mSelectedCard, mActivityStarter, animationController);
+ } else {
+ mController.startQuickAccessUiIntent(
+ mActivityStarter, animationController, mSelectedCard != null);
+ }
+ });
}
@Override
@@ -154,7 +163,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
Drawable tileIcon = mController.getWalletClient().getTileIcon();
state.icon =
tileIcon == null
- ? ResourceIcon.get(R.drawable.ic_wallet_lockscreen)
+ ? maybeLoadResourceIcon(R.drawable.ic_wallet_lockscreen)
: new DrawableIcon(tileIcon);
boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
if (mController.getWalletClient().isWalletServiceAvailable()
@@ -178,6 +187,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
state.secondaryLabel = null;
state.sideViewCustomDrawable = null;
}
+ state.expandedAccessibilityClassName = Button.class.getName();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 028ac6f4ac18..ca9d96ebf3e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -221,13 +221,13 @@ constructor(
state = Tile.STATE_ACTIVE
forceExpandIcon = false
secondaryLabel = mContext.getString(R.string.qs_record_issue_stop)
- icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on)
+ icon = maybeLoadResourceIcon(R.drawable.qs_record_issue_icon_on)
} else {
value = false
state = Tile.STATE_INACTIVE
forceExpandIcon = true
secondaryLabel = mContext.getString(R.string.qs_record_issue_start)
- icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off)
+ icon = maybeLoadResourceIcon(R.drawable.qs_record_issue_icon_off)
}
label = tileLabel
contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index d624d989f42a..26d43ee92bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -141,7 +141,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
state.label = mContext.getString(R.string.reduce_bright_colors_feature_name);
state.expandedAccessibilityClassName = Switch.class.getName();
state.contentDescription = state.label;
- state.icon = ResourceIcon.get(state.value
+ state.icon = maybeLoadResourceIcon(state.value
? drawable.qs_extra_dim_icon_on
: drawable.qs_extra_dim_icon_off);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 35e43b6fed9e..e361bb8ce883 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -63,7 +63,8 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
private static final String EMPTY_SECONDARY_STRING = "";
- private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
+ private final Icon mIcon =
+ maybeLoadResourceIcon(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
private final SensorPrivacyManager mPrivacyManager;
private final BatteryController mBatteryController;
@@ -153,13 +154,13 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
&& mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
- state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_off);
state.contentDescription = getAccessibilityString(rotationLocked);
if (!rotationLocked) {
state.secondaryLabel = cameraRotation ? mContext.getResources().getString(
R.string.rotation_lock_camera_rotation_on)
: EMPTY_SECONDARY_STRING;
- state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on);
+ state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_on);
} else {
state.secondaryLabel = EMPTY_SECONDARY_STRING;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index f3be340f4951..4fb96e72d8df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles;
import android.app.Dialog;
import android.content.Intent;
+import android.media.projection.StopReason;
import android.os.Handler;
import android.os.Looper;
import android.service.quicksettings.Tile;
@@ -138,9 +139,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
state.value = isRecording || isStarting;
state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.quick_settings_screen_record_label);
- state.icon = ResourceIcon.get(state.value
- ? R.drawable.qs_screen_record_icon_on
- : R.drawable.qs_screen_record_icon_off);
+ state.icon = maybeLoadResourceIcon(state.value
+ ? R.drawable.qs_screen_record_icon_on : R.drawable.qs_screen_record_icon_off);
// Show expand icon when clicking will open a dialog
state.forceExpandIcon = state.state == Tile.STATE_INACTIVE;
@@ -225,7 +225,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
}
private void stopRecording() {
- mController.stopRecording();
+ mController.stopRecording(StopReason.STOP_QS_TILE);
}
private final class Callback implements RecordingController.RecordingStateChangeCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 036ce080c543..b62e858e6ade 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -119,7 +119,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
checkIfRestrictionEnforcedByAdminOnly(state, getRestriction());
- state.icon = ResourceIcon.get(getIconRes(isBlocked));
+ state.icon = maybeLoadResourceIcon(getIconRes(isBlocked));
state.state = isBlocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
state.value = !isBlocked;
state.label = getTileLabel();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index bec6581e54c9..61beb6ca1a71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -42,6 +42,7 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.LocationController;
@@ -78,7 +79,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
BatteryController batteryController,
LocationController locationController
) {
@@ -166,7 +167,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
} else {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
- state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
+ state.icon = maybeLoadResourceIcon(state.state == Tile.STATE_ACTIVE
? R.drawable.qs_light_dark_theme_icon_on
: R.drawable.qs_light_dark_theme_icon_off);
state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 1750347fd2ae..f6f89f759e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,8 +53,8 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
public static final String TILE_SPEC = "work";
- private final Icon mIcon = ResourceIcon.get(
- com.android.internal.R.drawable.stat_sys_managed_profile_status);
+ @Nullable
+ private Icon mIcon = null;
private final ManagedProfileController mProfileController;
@@ -129,6 +129,11 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
state.value = mProfileController.isWorkModeEnabled();
}
+ if (mIcon == null) {
+ mIcon = maybeLoadResourceIcon(
+ com.android.internal.R.drawable.stat_sys_managed_profile_status);
+ }
+
state.icon = mIcon;
state.label = getTileLabel();
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 17b78ebf106c..e8c4274474e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.base.interactor
import android.annotation.WorkerThread
+import com.android.systemui.plugins.qs.TileDetailsViewModel
interface QSTileUserActionInteractor<DATA_TYPE> {
/**
@@ -27,4 +28,17 @@ interface QSTileUserActionInteractor<DATA_TYPE> {
* It's safe to run long running computations inside this function.
*/
@WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
+
+ /**
+ * Provides the [TileDetailsViewModel] for constructing the corresponding details view.
+ *
+ * This property is defined here to reuse the business logic. For example, reusing the user
+ * long-click as the go-to-settings callback in the details view.
+ * Subclasses can override this property to provide a specific [TileDetailsViewModel]
+ * implementation.
+ *
+ * @return The [TileDetailsViewModel] instance, or null if not implemented.
+ */
+ val detailsViewModel: TileDetailsViewModel?
+ get() = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 87f542e6ab39..224fa104168d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -17,8 +17,10 @@
package com.android.systemui.qs.tiles.base.viewmodel
import android.os.UserHandle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
@@ -58,11 +60,8 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
-import kotlinx.coroutines.withContext
/**
* Provides a hassle-free way to implement new tiles according to current System UI architecture
@@ -90,36 +89,38 @@ class QSTileViewModelImpl<DATA_TYPE>(
private val users: MutableStateFlow<UserHandle> =
MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
+
private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow()
+
private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow()
private val spec
get() = config.tileSpec
- private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow()
+ private val tileData: SharedFlow<DATA_TYPE?> = createTileDataFlow()
override val state: StateFlow<QSTileState?> =
tileData
.map { data ->
- withContext(uiBackgroundDispatcher) { mapper().map(config, data) }
- .also { state -> qsTileLogger.logStateUpdate(spec, state, data) }
+ data?.let {
+ mapper().map(config, it).also { state ->
+ qsTileLogger.logStateUpdate(spec, state, it)
+ }
+ }
}
- .stateIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- null,
- )
+ .flowOn(uiBackgroundDispatcher)
+ .stateIn(tileScope, SharingStarted.WhileSubscribed(), null)
+
override val isAvailable: StateFlow<Boolean> =
users
.flatMapLatest { tileDataInteractor().availability(it) }
.flowOn(backgroundDispatcher)
- .stateIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- true,
- )
+ .stateIn(tileScope, SharingStarted.WhileSubscribed(), true)
+
+ override val detailsViewModel: TileDetailsViewModel?
+ get() = userActionInteractor().detailsViewModel
override fun forceUpdate() {
- tileScope.launch { forceUpdates.emit(Unit) }
+ tileScope.launch(context = backgroundDispatcher) { forceUpdates.emit(Unit) }
}
override fun onUserChanged(user: UserHandle) {
@@ -131,9 +132,9 @@ class QSTileViewModelImpl<DATA_TYPE>(
userAction,
spec,
tileData.replayCache.isNotEmpty(),
- state.replayCache.isNotEmpty()
+ state.replayCache.isNotEmpty(),
)
- tileScope.launch { userInputs.emit(userAction) }
+ tileScope.launch(context = backgroundDispatcher) { userInputs.emit(userAction) }
}
override fun destroy() {
@@ -147,7 +148,7 @@ class QSTileViewModelImpl<DATA_TYPE>(
println(state.replayCache.lastOrNull().toString())
}
- private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+ private fun createTileDataFlow(): SharedFlow<DATA_TYPE?> =
users
.transformLatest { user ->
coroutineScope {
@@ -159,6 +160,7 @@ class QSTileViewModelImpl<DATA_TYPE>(
.onEach { qsTileLogger.logForceUpdate(spec) },
)
.onStart { qsTileLogger.logInitialRequest(spec) }
+ .flowOn(backgroundDispatcher)
.stateIn(this, SharingStarted.Eagerly, DataUpdateTrigger.InitialRequest)
tileDataInteractor()
.tileData(user, updateTriggers)
@@ -171,11 +173,8 @@ class QSTileViewModelImpl<DATA_TYPE>(
}
}
.distinctUntilChanged()
- .shareIn(
- tileScope,
- SharingStarted.WhileSubscribed(),
- replay = 1, // we only care about the most recent value
- )
+ .flowOn(backgroundDispatcher)
+ .stateIn(tileScope, SharingStarted.WhileSubscribed(), null)
/**
* Creates a user input flow which:
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 244f024625db..bed802163b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -149,7 +149,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
static final int MAX_WIFI_ENTRY_COUNT = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index d67057a2f476..34c2ec90f1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -19,18 +19,18 @@ package com.android.systemui.qs.tiles.impl.airplane.domain
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [AirplaneModeTileModel] to [QSTileState]. */
class AirplaneModeMapper
@Inject
-constructor(@Main private val resources: Resources, val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, val theme: Theme) :
QSTileDataToStateMapper<AirplaneModeTileModel> {
override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index 7322b8d098fd..a72992db4496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -19,12 +19,12 @@ package com.android.systemui.qs.tiles.impl.alarm.domain
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.time.SystemClock
import java.time.Instant
import java.time.LocalDateTime
@@ -36,7 +36,7 @@ import javax.inject.Inject
class AlarmTileMapper
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Theme,
private val clock: SystemClock,
) : QSTileDataToStateMapper<AlarmTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index 5b30e8d2c86b..e116d8cef2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.battery.ui
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [BatterySaverTileModel] to [QSTileState]. */
open class BatterySaverTileMapper
@Inject
-constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(
+ @ShadeDisplayAware protected val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index 7c90b3d87958..21b9f659dde4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.colorcorrection.domain
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [ColorCorrectionTileModel] to [QSTileState]. */
class ColorCorrectionTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 7e557ebe4639..2dfb1fc4fe98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -19,18 +19,18 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [FlashlightTileModel] to [QSTileState]. */
class FlashlightMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
QSTileDataToStateMapper<FlashlightTileModel> {
override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 9d44fc6ae25e..7f41cbd322dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.fontscaling.domain
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [FontScalingTileModel] to [QSTileState]. */
class FontScalingTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<FontScalingTileModel> {
override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index c3ac1f8d9a72..4c302b363c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.hearingdevices.domain
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [HearingDevicesTileModel] to [QSTileState]. */
class HearingDevicesTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<HearingDevicesTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<HearingDevicesTileModel> {
override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
index a963b2875154..fdb15b9c2615 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -18,15 +18,23 @@ package com.android.systemui.qs.tiles.impl.internet.domain.interactor
import android.content.Intent
import android.provider.Settings
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.connectivity.AccessPointController
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext
@@ -61,11 +69,18 @@ constructor(
wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled
}
is QSTileUserAction.LongClick -> {
- qsTileIntentUserActionHandler.handle(
- action.expandable,
- Intent(Settings.ACTION_WIFI_SETTINGS)
- )
+ handleLongClick(action.expandable)
}
}
}
+
+ override val detailsViewModel: TileDetailsViewModel =
+ InternetDetailsViewModel { handleLongClick(null) }
+
+ private fun handleLongClick(expandable:Expandable?){
+ qsTileIntentUserActionHandler.handle(
+ expandable,
+ Intent(Settings.ACTION_WIFI_SETTINGS)
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 3692c35472f2..8d35b2413bad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -19,18 +19,18 @@ package com.android.systemui.qs.tiles.impl.inversion.domain
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [ColorInversionTileModel] to [QSTileState]. */
class ColorInversionTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
QSTileDataToStateMapper<ColorInversionTileModel> {
override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index 3fe2a7734801..3557c1a4ac9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -19,16 +19,16 @@ package com.android.systemui.qs.tiles.impl.irecording
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
class IssueRecordingMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
QSTileDataToStateMapper<IssueRecordingModel> {
override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index 08432f685ea8..dfc24a10c491 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -19,18 +19,18 @@ package com.android.systemui.qs.tiles.impl.location.domain
import android.content.res.Resources
import android.content.res.Resources.Theme
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [LocationTileModel] to [QSTileState]. */
class LocationTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
QSTileDataToStateMapper<LocationTileModel> {
override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index eb8b23c2505a..594394f68d48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -18,13 +18,16 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import android.content.Intent
import android.provider.Settings
+import android.util.Log
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
@@ -35,16 +38,19 @@ constructor(
private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
// TODO(b/353896370): The domain layer should not have to depend on the UI layer.
private val dialogDelegate: ModesDialogDelegate,
+ private val zenModeInteractor: ZenModeInteractor,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
with(input) {
when (action) {
- is QSTileUserAction.Click,
- is QSTileUserAction.ToggleClick -> {
+ is QSTileUserAction.Click -> {
handleClick(action.expandable)
}
+ is QSTileUserAction.ToggleClick -> {
+ handleToggleClick(input.data)
+ }
is QSTileUserAction.LongClick -> {
qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
}
@@ -56,4 +62,29 @@ constructor(
// Show a dialog with the list of modes to configure.
dialogDelegate.showDialog(expandable)
}
+
+ fun handleToggleClick(modesTileModel: ModesTileModel) {
+ if (QSComposeFragment.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+
+ // If no modes are on, turn on DND since it's the highest-priority mode. Otherwise, turn
+ // them all off.
+ // We want this toggle to work as a shortcut to DND in most cases, but it should still
+ // correctly toggle the tile state to "off" as the user would expect when more modes are on.
+ if (modesTileModel.activeModes.isEmpty()) {
+ val dnd = zenModeInteractor.dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return
+ }
+ zenModeInteractor.activateMode(dnd)
+ } else {
+ zenModeInteractor.deactivateAllModes()
+ }
+ }
+
+ companion object {
+ const val TAG = "ModesTileUserActionInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 4a6431359ca2..1507ef4b3b58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -19,18 +19,18 @@ package com.android.systemui.qs.tiles.impl.modes.ui
import android.content.res.Resources
import android.icu.text.MessageFormat
import android.widget.Button
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import java.util.Locale
import javax.inject.Inject
class ModesTileMapper
@Inject
-constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, val theme: Resources.Theme) :
QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -45,7 +45,11 @@ constructor(@Main private val resources: Resources, val theme: Resources.Theme)
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(
+ QSTileState.UserAction.CLICK,
+ QSTileState.UserAction.LONG_CLICK,
+ QSTileState.UserAction.TOGGLE_CLICK,
+ )
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
index 88bd224881b5..e8e43e8fc749 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
@@ -20,10 +20,10 @@ import android.content.Context
import android.hardware.display.ColorDisplayManager
import android.os.UserHandle
import com.android.systemui.accessibility.data.repository.NightDisplayRepository
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.time.DateFormatUtil
import java.time.LocalTime
import javax.inject.Inject
@@ -35,14 +35,14 @@ import kotlinx.coroutines.flow.map
class NightDisplayTileDataInteractor
@Inject
constructor(
- @Application private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val dateFormatUtil: DateFormatUtil,
private val nightDisplayRepository: NightDisplayRepository,
) : QSTileDataInteractor<NightDisplayTileModel> {
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<NightDisplayTileModel> =
nightDisplayRepository.nightDisplayState(user).map {
generateModel(
@@ -51,7 +51,7 @@ constructor(
it.startTime,
it.endTime,
it.shouldForceAutoMode,
- it.locationEnabled
+ it.locationEnabled,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index 081a03c7ae67..3569e4d0b42c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -22,7 +22,6 @@ import android.text.TextUtils
import androidx.annotation.StringRes
import com.android.systemui.accessibility.qs.QSAccessibilityModule
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
@@ -30,6 +29,7 @@ import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileMod
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import java.time.DateTimeException
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@@ -39,7 +39,7 @@ import javax.inject.Inject
class NightDisplayTileMapper
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Resources.Theme,
private val logger: QSTileLogger,
) : QSTileDataToStateMapper<NightDisplayTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
index ee1b9e5171b7..a5436192af39 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
@@ -19,28 +19,24 @@ package com.android.systemui.qs.tiles.impl.notes.domain
import android.content.res.Resources
import android.widget.Button
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
class NotesTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<NotesTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<NotesTileModel> {
override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
iconRes = R.drawable.ic_qs_notes
- icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme),
- contentDescription = null
- )
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
contentDescription = label
activationState = QSTileState.ActivationState.INACTIVE
sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 8e5d0d4eb3dc..76f1e8b8760c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.onehanded.ui
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [OneHandedModeTileModel] to [QSTileState]. */
class OneHandedModeTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<OneHandedModeTileModel> {
override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 5c6351e88494..c546250e73d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.qr.ui
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
class QRCodeScannerTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
index 15c9901d10c2..eff5f8f949c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -20,20 +20,20 @@ import android.content.Intent
import android.content.res.Resources
import android.provider.Settings
import com.android.systemui.accessibility.extradim.ExtraDimDialogManager
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.ReduceBrightColorsController
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Handles reduce bright colors tile clicks. */
class ReduceBrightColorsTileUserActionInteractor
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
private val reduceBrightColorsController: ReduceBrightColorsController,
private val extraDimDialogManager: ExtraDimDialogManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index fe77fe61b4bf..66d0f96fdcde 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -19,19 +19,21 @@ package com.android.systemui.qs.tiles.impl.reducebrightness.ui
import android.content.res.Resources
import android.service.quicksettings.Tile
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
class ReduceBrightColorsTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
index 57a60c179581..7f17a3a7481a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
@@ -22,10 +22,10 @@ import android.content.res.Resources
import android.os.UserHandle
import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.RotationLockController
import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
@@ -44,30 +44,29 @@ constructor(
private val cameraAutoRotateRepository: CameraAutoRotateRepository,
private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository,
private val packageManager: PackageManager,
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
) : QSTileDataInteractor<RotationLockTileModel> {
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<RotationLockTileModel> =
combine(
rotationLockController.isRotationLockEnabled(),
cameraSensorPrivacyRepository.isEnabled(user),
batteryController.isBatteryPowerSaveEnabled(),
- cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user)
+ cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user),
) {
isRotationLockEnabled,
isCamPrivacySensorEnabled,
isBatteryPowerSaveEnabled,
- isCameraAutoRotateEnabled,
- ->
+ isCameraAutoRotateEnabled ->
RotationLockTileModel(
isRotationLockEnabled,
isCameraRotationEnabled(
isBatteryPowerSaveEnabled,
isCamPrivacySensorEnabled,
- isCameraAutoRotateEnabled
+ isCameraAutoRotateEnabled,
),
)
}
@@ -84,7 +83,7 @@ constructor(
private fun isCameraRotationEnabled(
isBatteryPowerSaverModeOn: Boolean,
isCameraSensorPrivacyEnabled: Boolean,
- isCameraAutoRotateEnabled: Boolean
+ isCameraAutoRotateEnabled: Boolean,
): Boolean =
resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) &&
!isBatteryPowerSaverModeOn &&
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 9a003ffdf7de..a0144221577d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -19,12 +19,12 @@ package com.android.systemui.qs.tiles.impl.rotation.ui.mapper
import android.content.res.Resources
import android.hardware.devicestate.DeviceStateManager
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.util.Utils.isDeviceFoldable
import javax.inject.Inject
@@ -33,7 +33,7 @@ import javax.inject.Inject
class RotationLockTileMapper
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Resources.Theme,
private val devicePostureController: DevicePostureController,
private val deviceStateManager: DeviceStateManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 08196bbfe2f3..aea4967c546c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -18,19 +18,21 @@ package com.android.systemui.qs.tiles.impl.saver.domain
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [DataSaverTileModel] to [QSTileState]. */
class DataSaverTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<DataSaverTileModel> {
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
with(data) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
index 85aa6745e438..94534479db57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
+import android.media.projection.StopReason
import android.util.Log
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
@@ -61,7 +62,9 @@ constructor(
Log.d(TAG, "Cancelling countdown")
withContext(backgroundContext) { recordingController.cancelCountdown() }
}
- is ScreenRecordModel.Recording -> screenRecordRepository.stopRecording()
+ is ScreenRecordModel.Recording -> {
+ screenRecordRepository.stopRecording(StopReason.STOP_QS_TILE)
+ }
is ScreenRecordModel.DoingNothing ->
withContext(mainContext) {
showPrompt(action.expandable, user.identifier)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index ba06de966c10..f3136e015acf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -19,19 +19,21 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.ui
import android.content.res.Resources
import android.text.TextUtils
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [ScreenRecordModel] to [QSTileState]. */
class ScreenRecordTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
- QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(
+ @ShadeDisplayAware private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ScreenRecordModel> {
override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_screen_record_label)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index b4cfec48fb0a..73e61b7d178e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -18,12 +18,12 @@ package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
import android.content.res.Resources
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -32,7 +32,7 @@ import dagger.assisted.AssistedInject
class SensorPrivacyToggleTileMapper
@AssistedInject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Resources.Theme,
@Assisted private val sensorPrivacyTileResources: SensorPrivacyTileResources,
) : QSTileDataToStateMapper<SensorPrivacyToggleTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index eda8e5ce8c43..e9aa46c5f253 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -21,12 +21,12 @@ import android.content.res.Resources
import android.content.res.Resources.Theme
import android.text.TextUtils
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
/** Maps [UiModeNightTileModel] to [QSTileState]. */
class UiModeNightTileMapper
@Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
QSTileDataToStateMapper<UiModeNightTileModel> {
companion object {
val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
index 7af3576d8cd9..925b91326a25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
@@ -21,7 +21,6 @@ import android.content.Context
import android.content.res.Configuration
import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
@@ -40,7 +39,7 @@ class UiModeNightTileDataInteractor
@Inject
constructor(
@ShadeDisplayAware private val context: Context,
- private val configurationController: ConfigurationController,
+ @ShadeDisplayAware private val configurationController: ConfigurationController,
private val uiModeManager: UiModeManager,
private val batteryController: BatteryController,
private val locationController: LocationController,
@@ -49,7 +48,7 @@ constructor(
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<UiModeNightTileModel> =
ConflatedCallbackFlow.conflatedCallbackFlow {
// send initial state
@@ -106,7 +105,7 @@ constructor(
nightModeCustomType,
use24HourFormat,
customNightModeEnd,
- customNightModeStart
+ customNightModeStart,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index a1bc8a889a1b..6a3195a493c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -21,19 +21,19 @@ import android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_
import android.content.res.Resources
import android.service.quicksettings.Tile
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/** Maps [WorkModeTileModel] to [QSTileState]. */
class WorkModeTileMapper
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Resources.Theme,
private val devicePolicyManager: DevicePolicyManager,
) : QSTileDataToStateMapper<WorkModeTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index b1b0001b6361..e8b9926e5cea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.viewmodel
import android.os.UserHandle
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import kotlinx.coroutines.flow.StateFlow
/**
@@ -37,6 +38,10 @@ interface QSTileViewModel {
/** Specifies whether this device currently supports this tile. */
val isAvailable: StateFlow<Boolean>
+ /** Specifies the [TileDetailsViewModel] for constructing the corresponding details view. */
+ val detailsViewModel: TileDetailsViewModel?
+ get() = null
+
/**
* Notifies about the user change. Implementations should avoid using 3rd party userId sources
* and use this value instead. This is to maintain consistent and concurrency-free behaviour
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f9a1ad5d8424..632eeefcb462 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -27,6 +27,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
@@ -154,6 +155,10 @@ constructor(
qsTileViewModel.onUserChanged(UserHandle.of(currentUser))
}
+ override fun getDetailsViewModel(): TileDetailsViewModel? {
+ return qsTileViewModel.detailsViewModel
+ }
+
@Deprecated(
"Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec",
replaceWith = ReplaceWith("getMetricsSpec"),
@@ -207,8 +212,9 @@ constructor(
qsTileViewModel.destroy()
}
- override fun getState(): QSTile.State? =
+ override fun getState(): QSTile.State =
qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+ ?: QSTile.State()
override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index 62b120332289..91d907952bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -22,6 +22,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -38,6 +39,7 @@ constructor(
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
val detailsViewModel: DetailsViewModel,
+ val toolbarViewModelFactory: ToolbarViewModel.Factory,
) : ExclusiveActivatable() {
val brightnessSliderViewModel =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a5eb92b10239..e3cf41191384 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,11 +26,6 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.window.BackEvent.EDGE_NONE;
-import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
-import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING;
@@ -42,6 +37,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
+import static com.android.systemui.shared.system.QuickStepContract.addInterface;
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
+import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
import android.annotation.FloatRange;
import android.annotation.Nullable;
@@ -559,13 +557,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
- params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
- params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
- mSysuiUnlockAnimationController.asBinder());
- mUnfoldTransitionProgressForwarder.ifPresent(
- unfoldProgressForwarder -> params.putBinder(
- KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
- unfoldProgressForwarder.asBinder()));
+ addInterface(mSysUiProxy, params);
+ addInterface(mSysuiUnlockAnimationController, params);
+ addInterface(mUnfoldTransitionProgressForwarder.orElse(null), params);
// Add all the interfaces exposed by the shell
mShellInterface.createExternalInterfaces(params);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index e441a23d3725..e36e40d312b5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -98,6 +99,7 @@ interface KeyguardlessSceneContainerFrameworkModule {
Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Gone,
+ transitions = SceneContainerTransitions,
overlayKeys =
listOfNotNull(
Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 4beec1041a03..fe014524e3da 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@ import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
@@ -106,6 +107,7 @@ interface SceneContainerFrameworkModule {
Scenes.Shade.takeUnless { DualShade.isEnabled },
),
initialSceneKey = Scenes.Lockscreen,
+ transitions = SceneContainerTransitions,
overlayKeys =
listOfNotNull(
Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 16ed59f4e6f2..c1646b8f2060 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -20,6 +20,7 @@ import com.android.systemui.scene.domain.SceneDomainModule
import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import dagger.Module
import dagger.Provides
@@ -35,7 +36,7 @@ import dagger.Provides
// List SceneResolver modules for supported SceneFamilies
HomeSceneFamilyResolverModule::class,
- ],
+ ]
)
object ShadelessSceneContainerFrameworkModule {
@@ -46,20 +47,12 @@ object ShadelessSceneContainerFrameworkModule {
return SceneContainerConfig(
// Note that this list is in z-order. The first one is the bottom-most and the
// last one is top-most.
- sceneKeys =
- listOf(
- Scenes.Gone,
- Scenes.Lockscreen,
- Scenes.Bouncer,
- ),
+ sceneKeys = listOf(Scenes.Gone, Scenes.Lockscreen, Scenes.Bouncer),
initialSceneKey = Scenes.Lockscreen,
+ transitions = SceneContainerTransitions,
overlayKeys = emptyList(),
navigationDistances =
- mapOf(
- Scenes.Gone to 0,
- Scenes.Lockscreen to 0,
- Scenes.Bouncer to 1,
- )
+ mapOf(Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Bouncer to 1),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 2311e47abfae..ce7be8311c68 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -18,6 +18,7 @@ package com.android.systemui.scene.shared.model
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitions
/** Models the configuration of the scene container. */
data class SceneContainerConfig(
@@ -38,6 +39,9 @@ data class SceneContainerConfig(
*/
val initialSceneKey: SceneKey,
+ /** Transition definitions to be used when animating between scene transitions. */
+ val transitions: SceneTransitions,
+
/**
* The keys to all overlays in the container, sorted by z-order such that the last one renders
* on top of all previous ones. Overlay keys within the same container must not repeat but it's
@@ -61,7 +65,7 @@ data class SceneContainerConfig(
* Note that this is not the z-order of rendering; that's determined by the order of declaration
* of scenes in the [sceneKeys] list.
*/
- val navigationDistances: Map<SceneKey, Int>
+ val navigationDistances: Map<SceneKey, Int>,
) {
init {
check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
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 7d7cab41cf96..c45906840385 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
@@ -5,7 +5,6 @@ import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.WindowInsets
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -35,7 +34,6 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
- alternateBouncerDependencies: AlternateBouncerDependencies,
) {
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
@@ -54,7 +52,6 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
},
dataSourceDelegator = sceneDataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
- alternateBouncerDependencies = alternateBouncerDependencies,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 1e3a233bfc7e..f7061d9af961 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -36,15 +36,12 @@ import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
-import com.android.systemui.keyguard.ui.composable.AlternateBouncer
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Overlay
@@ -77,7 +74,6 @@ object SceneWindowRootViewBinder {
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
- alternateBouncerDependencies: AlternateBouncerDependencies,
) {
val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
val sortedSceneByKey: Map<SceneKey, Scene> =
@@ -148,20 +144,10 @@ object SceneWindowRootViewBinder {
// the SceneContainerView. This SharedNotificationContainer should contain NSSL
// due to the NotificationStackScrollLayoutSection (legacy) or
// NotificationSection (scene container) moving it there.
- if (SceneContainerFlag.isEnabled) {
- (sharedNotificationContainer.parent as? ViewGroup)?.removeView(
- sharedNotificationContainer
- )
- view.addView(sharedNotificationContainer)
-
- // TODO(b/358354906): use an overlay for the alternate bouncer
- view.addView(
- createAlternateBouncerView(
- context = view.context,
- alternateBouncerDependencies = alternateBouncerDependencies,
- )
- )
- }
+ (sharedNotificationContainer.parent as? ViewGroup)?.removeView(
+ sharedNotificationContainer
+ )
+ view.addView(sharedNotificationContainer)
view.setSnapshotBinding { onVisibilityChangedInternal(viewModel.isVisible) }
awaitCancellation()
@@ -196,6 +182,7 @@ object SceneWindowRootViewBinder {
sceneByKey = sceneByKey,
overlayByKey = overlayByKey,
initialSceneKey = containerConfig.initialSceneKey,
+ sceneTransitions = containerConfig.transitions,
dataSourceDelegator = dataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
)
@@ -205,17 +192,6 @@ object SceneWindowRootViewBinder {
}
}
- private fun createAlternateBouncerView(
- context: Context,
- alternateBouncerDependencies: AlternateBouncerDependencies,
- ): ComposeView {
- return ComposeView(context).apply {
- setContent {
- AlternateBouncer(alternateBouncerDependencies = alternateBouncerDependencies)
- }
- }
- }
-
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun displayCutoutFromWindowInsets(
scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index d7463f8f0c36..9ee99e45ceeb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.projection.StopReason;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Process;
@@ -58,6 +59,7 @@ public class RecordingController
private boolean mIsStarting;
private boolean mIsRecording;
private PendingIntent mStopIntent;
+ private @StopReason int mStopReason = StopReason.STOP_UNKNOWN;
private final Bundle mInteractiveBroadcastOption;
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
@@ -83,7 +85,7 @@ public class RecordingController
new UserTracker.Callback() {
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
- stopRecording();
+ stopRecording(StopReason.STOP_USER_SWITCH);
}
};
@@ -240,9 +242,11 @@ public class RecordingController
}
/**
- * Stop the recording
+ * Stop the recording and sets the stop reason to be used by the RecordingService
+ * @param stopReason the method of the recording stopped (i.e. QS tile, status bar chip, etc.)
*/
- public void stopRecording() {
+ public void stopRecording(@StopReason int stopReason) {
+ mStopReason = stopReason;
try {
if (mStopIntent != null) {
mRecordingControllerLogger.logRecordingStopped();
@@ -277,6 +281,10 @@ public class RecordingController
}
}
+ public @StopReason int getStopReason() {
+ return mStopReason;
+ }
+
@Override
public void addCallback(@NonNull RecordingStateChangeCallback listener) {
mListeners.add(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 8c207d13d50e..f7b52719a4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
+import android.media.projection.StopReason;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -78,6 +79,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
private static final String EXTRA_DISPLAY_ID = "extra_displayId";
+ private static final String EXTRA_STOP_REASON = "extra_stopReason";
protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
protected static final String ACTION_SHOW_START_NOTIF =
@@ -242,7 +244,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
// Check user ID - we may be getting a stop intent after user switch, in which case
// we want to post the notifications for that user, which is NOT current user
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
- stopService(userId);
+ int stopReason = intent.getIntExtra(EXTRA_STOP_REASON, mController.getStopReason());
+ stopService(userId, stopReason);
break;
case ACTION_SHARE:
@@ -486,11 +489,11 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
getTag(), notificationIdForGroup, groupNotif, currentUser);
}
- private void stopService() {
- stopService(USER_ID_NOT_SPECIFIED);
+ private void stopService(@StopReason int stopReason) {
+ stopService(USER_ID_NOT_SPECIFIED, stopReason);
}
- private void stopService(int userId) {
+ private void stopService(int userId, @StopReason int stopReason) {
if (userId == USER_ID_NOT_SPECIFIED) {
userId = mUserContextTracker.getUserContext().getUserId();
}
@@ -499,7 +502,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
setTapsVisible(mOriginalShowTaps);
try {
if (getRecorder() != null) {
- getRecorder().end();
+ getRecorder().end(stopReason);
}
saveRecording(userId);
} catch (RuntimeException exception) {
@@ -598,7 +601,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
* @return
*/
protected Intent getNotificationIntent(Context context) {
- return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
+ return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF)
+ .putExtra(EXTRA_STOP_REASON, StopReason.STOP_HOST_APP);
}
private Intent getShareIntent(Context context, Uri path) {
@@ -610,14 +614,17 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
Log.d(getTag(), "Media recorder info: " + what);
- onStartCommand(getStopIntent(this), 0, 0);
+ // Stop due to record reaching size limits so log as stopping due to error
+ Intent stopIntent = getStopIntent(this);
+ stopIntent.putExtra(EXTRA_STOP_REASON, StopReason.STOP_ERROR);
+ onStartCommand(stopIntent, 0, 0);
}
@Override
- public void onStopped() {
+ public void onStopped(@StopReason int stopReason) {
if (mController.isRecording()) {
Log.d(getTag(), "Stopping recording because the system requested the stop");
- stopService();
+ stopService(stopReason);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2ca0621635a7..f4455bfb7048 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -41,6 +41,7 @@ import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
+import android.media.projection.StopReason;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -300,7 +301,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
/**
* End screen recording, throws an exception if stopping recording failed
*/
- void end() throws IOException {
+ void end(@StopReason int stopReason) throws IOException {
Closer closer = new Closer();
// MediaRecorder might throw RuntimeException if stopped immediately after starting
@@ -309,7 +310,17 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
closer.register(mMediaRecorder::release);
closer.register(mInputSurface::release);
closer.register(mVirtualDisplay::release);
- closer.register(mMediaProjection::stop);
+ closer.register(() -> {
+ if (stopReason == StopReason.STOP_UNKNOWN) {
+ // Attempt to call MediaProjection#stop() even if it might have already been called.
+ // If projection has already been stopped, then nothing will happen. Else, stop
+ // will be logged as a manually requested stop from host app.
+ mMediaProjection.stop();
+ } else {
+ // In any other case, the stop reason is related to the recorder, so pass it on here
+ mMediaProjection.stop(stopReason);
+ }
+ });
closer.register(this::stopInternalAudioRecording);
closer.close();
@@ -323,7 +334,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
@Override
public void onStop() {
Log.d(TAG, "The system notified about stopping the projection");
- mListener.onStopped();
+ mListener.onStopped(StopReason.STOP_UNKNOWN);
}
private void stopInternalAudioRecording() {
@@ -453,7 +464,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
* For example, this might happen when doing partial screen sharing of an app
* and the app that is being captured is closed.
*/
- void onStopped();
+ void onStopped(@StopReason int stopReason);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index bdc58c1ceeb1..1da4c1d9469d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -21,7 +21,6 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.hardware.display.DisplayManager
-import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -30,8 +29,6 @@ import android.os.UserHandle
import android.view.Display
import android.view.MotionEvent.ACTION_MOVE
import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.AdapterView
import android.widget.ArrayAdapter
@@ -44,10 +41,10 @@ import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.BaseMediaProjectionPermissionDialogDelegate
+import com.android.systemui.mediaprojection.permission.BaseMediaProjectionPermissionViewBinder
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
import com.android.systemui.mediaprojection.permission.SINGLE_APP
import com.android.systemui.mediaprojection.permission.ScreenShareMode
-import com.android.systemui.mediaprojection.permission.ScreenShareOption
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
@@ -64,15 +61,15 @@ class ScreenRecordPermissionDialogDelegate(
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
private val onStartRecordingClicked: Runnable?,
- mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val systemUIDialogFactory: SystemUIDialog.Factory,
@ScreenShareMode defaultSelectedMode: Int,
@StyleRes private val theme: Int,
private val context: Context,
- displayManager: DisplayManager,
+ private val displayManager: DisplayManager,
) :
BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
- createOptionList(displayManager),
+ ScreenRecordPermissionViewBinder.createOptionList(displayManager),
appName = null,
hostUid = hostUid,
mediaProjectionMetricsLogger,
@@ -119,10 +116,19 @@ class ScreenRecordPermissionDialogDelegate(
}
private lateinit var tapsSwitch: Switch
- private lateinit var tapsView: View
private lateinit var audioSwitch: Switch
private lateinit var options: Spinner
+ override fun createViewBinder(): BaseMediaProjectionPermissionViewBinder {
+ return ScreenRecordPermissionViewBinder(
+ hostUid,
+ mediaProjectionMetricsLogger,
+ defaultSelectedMode,
+ displayManager,
+ dialog,
+ )
+ }
+
override fun createDialog(): SystemUIDialog {
return systemUIDialogFactory.create(this, context, theme)
}
@@ -133,6 +139,7 @@ class ScreenRecordPermissionDialogDelegate(
dialog.setTitle(R.string.screenrecord_title)
setStartButtonOnClickListener { v: View? ->
onStartRecordingClicked?.run()
+ val selectedScreenShareOption = getSelectedScreenShareOption()
if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
requestScreenCapture(/* captureTarget= */ null, selectedScreenShareOption.displayId)
}
@@ -168,6 +175,7 @@ class ScreenRecordPermissionDialogDelegate(
@SuppressLint("ClickableViewAccessibility")
private fun initRecordOptionsView() {
+ // TODO(b/378514312): Move this function to ScreenRecordPermissionViewBinder
audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch)
tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch)
@@ -176,9 +184,6 @@ class ScreenRecordPermissionDialogDelegate(
audioSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
tapsSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
- tapsView = dialog.requireViewById(R.id.show_taps)
- updateTapsViewVisibility()
-
options = dialog.requireViewById(R.id.screen_recording_options)
val a: ArrayAdapter<*> =
ScreenRecordingAdapter(
@@ -206,15 +211,6 @@ class ScreenRecordPermissionDialogDelegate(
options.isLongClickable = false
}
- override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
- super.onItemSelected(adapterView, view, pos, id)
- updateTapsViewVisibility()
- }
-
- private fun updateTapsViewVisibility() {
- tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
- }
-
/**
* Starts screen capture after some countdown
*
@@ -226,7 +222,7 @@ class ScreenRecordPermissionDialogDelegate(
displayId: Int = Display.DEFAULT_DISPLAY,
) {
val userContext = userContextProvider.userContext
- val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked
+ val showTaps = getSelectedScreenShareOption().mode != SINGLE_APP && tapsSwitch.isChecked
val audioMode =
if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
else ScreenRecordingAudioSource.NONE
@@ -279,65 +275,5 @@ class ScreenRecordPermissionDialogDelegate(
)
private const val DELAY_MS: Long = 3000
private const val INTERVAL_MS: Long = 1000
-
- private fun createOptionList(displayManager: DisplayManager): List<ScreenShareOption> {
- if (!com.android.media.projection.flags.Flags.mediaProjectionConnectedDisplay()) {
- return listOf(
- ScreenShareOption(
- SINGLE_APP,
- R.string.screenrecord_permission_dialog_option_text_single_app,
- R.string.screenrecord_permission_dialog_warning_single_app,
- startButtonText =
- R.string
- .media_projection_entry_generic_permission_dialog_continue_single_app,
- ),
- ScreenShareOption(
- ENTIRE_SCREEN,
- R.string.screenrecord_permission_dialog_option_text_entire_screen,
- R.string.screenrecord_permission_dialog_warning_entire_screen,
- startButtonText =
- R.string.screenrecord_permission_dialog_continue_entire_screen,
- displayId = Display.DEFAULT_DISPLAY,
- displayName = Build.MODEL,
- ),
- )
- }
- return listOf(
- ScreenShareOption(
- SINGLE_APP,
- R.string.screenrecord_permission_dialog_option_text_single_app,
- R.string.screenrecord_permission_dialog_warning_single_app,
- startButtonText =
- R.string
- .media_projection_entry_generic_permission_dialog_continue_single_app,
- ),
- ScreenShareOption(
- ENTIRE_SCREEN,
- R.string.screenrecord_permission_dialog_option_text_entire_screen_for_display,
- R.string.screenrecord_permission_dialog_warning_entire_screen,
- startButtonText =
- R.string.screenrecord_permission_dialog_continue_entire_screen,
- displayId = Display.DEFAULT_DISPLAY,
- displayName = Build.MODEL,
- ),
- ) +
- displayManager.displays
- .filter { it.displayId != Display.DEFAULT_DISPLAY }
- .map {
- ScreenShareOption(
- ENTIRE_SCREEN,
- R.string
- .screenrecord_permission_dialog_option_text_entire_screen_for_display,
- warningText =
- R.string
- .media_projection_entry_app_permission_dialog_warning_entire_screen,
- startButtonText =
- R.string
- .media_projection_entry_app_permission_dialog_continue_entire_screen,
- displayId = it.displayId,
- displayName = it.name,
- )
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt
new file mode 100644
index 000000000000..91c6b4769c99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.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.screenrecord
+
+import android.annotation.SuppressLint
+import android.app.AlertDialog
+import android.hardware.display.DisplayManager
+import android.os.Build
+import android.view.Display
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.permission.BaseMediaProjectionPermissionViewBinder
+import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
+import com.android.systemui.mediaprojection.permission.SINGLE_APP
+import com.android.systemui.mediaprojection.permission.ScreenShareMode
+import com.android.systemui.mediaprojection.permission.ScreenShareOption
+import com.android.systemui.res.R
+
+class ScreenRecordPermissionViewBinder(
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ @ScreenShareMode defaultSelectedMode: Int,
+ displayManager: DisplayManager,
+ private val dialog: AlertDialog,
+) :
+ BaseMediaProjectionPermissionViewBinder(
+ createOptionList(displayManager),
+ appName = null,
+ hostUid = hostUid,
+ mediaProjectionMetricsLogger,
+ defaultSelectedMode,
+ dialog,
+ ) {
+ private lateinit var tapsView: View
+
+ override fun bind() {
+ super.bind()
+ initRecordOptionsView()
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun initRecordOptionsView() {
+ tapsView = dialog.requireViewById(R.id.show_taps)
+ updateTapsViewVisibility()
+ }
+
+ override fun onItemSelected(pos: Int) {
+ super.onItemSelected(pos)
+ updateTapsViewVisibility()
+ }
+
+ private fun updateTapsViewVisibility() {
+ tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
+ }
+
+ companion object {
+ private val RECORDABLE_DISPLAY_TYPES =
+ intArrayOf(
+ Display.TYPE_OVERLAY,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_INTERNAL,
+ Display.TYPE_WIFI,
+ )
+
+ private val filterDeviceTypeFlag: Boolean =
+ com.android.media.projection.flags.Flags
+ .mediaProjectionConnectedDisplayNoVirtualDevice()
+
+ fun createOptionList(displayManager: DisplayManager): List<ScreenShareOption> {
+ if (!com.android.media.projection.flags.Flags.mediaProjectionConnectedDisplay()) {
+ return listOf(
+ ScreenShareOption(
+ SINGLE_APP,
+ R.string.screenrecord_permission_dialog_option_text_single_app,
+ R.string.screenrecord_permission_dialog_warning_single_app,
+ startButtonText =
+ R.string
+ .media_projection_entry_generic_permission_dialog_continue_single_app,
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.screenrecord_permission_dialog_option_text_entire_screen,
+ R.string.screenrecord_permission_dialog_warning_entire_screen,
+ startButtonText =
+ R.string.screenrecord_permission_dialog_continue_entire_screen,
+ displayId = Display.DEFAULT_DISPLAY,
+ displayName = Build.MODEL,
+ ),
+ )
+ }
+
+ return listOf(
+ ScreenShareOption(
+ SINGLE_APP,
+ R.string.screenrecord_permission_dialog_option_text_single_app,
+ R.string.screenrecord_permission_dialog_warning_single_app,
+ startButtonText =
+ R.string
+ .media_projection_entry_generic_permission_dialog_continue_single_app,
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.screenrecord_permission_dialog_option_text_entire_screen_for_display,
+ R.string.screenrecord_permission_dialog_warning_entire_screen,
+ startButtonText =
+ R.string.screenrecord_permission_dialog_continue_entire_screen,
+ displayId = Display.DEFAULT_DISPLAY,
+ displayName = Build.MODEL,
+ ),
+ ) +
+ displayManager.displays
+ .filter {
+ it.displayId != Display.DEFAULT_DISPLAY &&
+ (!filterDeviceTypeFlag || it.type in RECORDABLE_DISPLAY_TYPES)
+ }
+ .map {
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string
+ .screenrecord_permission_dialog_option_text_entire_screen_for_display,
+ warningText =
+ R.string
+ .media_projection_entry_app_permission_dialog_warning_entire_screen,
+ startButtonText =
+ R.string
+ .media_projection_entry_app_permission_dialog_continue_entire_screen,
+ displayId = it.displayId,
+ displayName = it.name,
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
index 9eeb3b9576d8..b6b8ffa11495 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord.data.repository
+import android.media.projection.StopReason
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenrecord.RecordingController
@@ -41,7 +42,7 @@ interface ScreenRecordRepository {
val screenRecordState: Flow<ScreenRecordModel>
/** Stops the recording. */
- suspend fun stopRecording()
+ suspend fun stopRecording(@StopReason stopReason: Int)
}
@SysUISingleton
@@ -95,7 +96,7 @@ constructor(
}
}
- override suspend fun stopRecording() {
- withContext(bgCoroutineContext) { recordingController.stopRecording() }
+ override suspend fun stopRecording(@StopReason stopReason: Int) {
+ withContext(bgCoroutineContext) { recordingController.stopRecording(stopReason) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 7b01c36489fb..a365b7c5e9a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -37,7 +37,7 @@ import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.screenshot.proxy.SystemUiProxy
+import com.android.systemui.screenshot.proxy.ScreenshotProxy
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -55,7 +55,7 @@ constructor(
private val activityManagerWrapper: ActivityManagerWrapper,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- private val systemUiProxy: SystemUiProxy,
+ private val screenshotProxy: ScreenshotProxy,
private val displayTracker: DisplayTracker,
) {
/**
@@ -89,7 +89,7 @@ constructor(
CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT
)
}
- systemUiProxy.dismissKeyguard()
+ screenshotProxy.dismissKeyguard()
var transitionOptions: ActivityOptions? = null
if (transitionCoordinator?.decor?.isAttachedToWindow == true) {
transitionCoordinator.startExit()
@@ -127,7 +127,7 @@ constructor(
private suspend fun launchCrossProfileIntent(
user: UserHandle,
intent: Intent,
- bundle: Bundle?
+ bundle: Bundle?,
) {
val connector = getCrossProfileConnector(user)
val completion = CompletableDeferred<Unit>()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 90695fa00ceb..253e3a613083 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -38,7 +38,7 @@ import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
import com.android.systemui.screenshot.message.MessageModule;
import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
-import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
+import com.android.systemui.screenshot.proxy.ScreenshotProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
import dagger.Binds;
@@ -50,7 +50,7 @@ import dagger.multibindings.IntoMap;
/**
* Defines injectable resources for Screenshots
*/
-@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class, MessageModule.class})
+@Module(includes = {ScreenshotPolicyModule.class, ScreenshotProxyModule.class, MessageModule.class})
public abstract class ScreenshotModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
index e9599dcb026d..ec34808459c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
@@ -22,21 +22,21 @@ import android.app.IActivityTaskManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.SystemUiState
-import com.android.systemui.screenshot.proxy.SystemUiProxy
+import com.android.systemui.screenshot.proxy.ScreenshotProxy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
/**
* Implements DisplayTaskRepository using [IActivityTaskManager], along with [ProfileTypeRepository]
- * and [SystemUiProxy].
+ * and [ScreenshotProxy].
*/
@SuppressLint("MissingPermission")
class DisplayContentRepositoryImpl
@Inject
constructor(
private val atmService: IActivityTaskManager,
- private val systemUiProxy: SystemUiProxy,
+ private val screenshotProxy: ScreenshotProxy,
@Background private val background: CoroutineDispatcher,
) : DisplayContentRepository {
@@ -53,8 +53,8 @@ constructor(
): DisplayContentModel {
return DisplayContentModel(
displayId,
- SystemUiState(systemUiProxy.isNotificationShadeExpanded()),
- rootTasks
+ SystemUiState(screenshotProxy.isNotificationShadeExpanded()),
+ rootTasks,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/IOnDoneCallback.aidl
index e15030f78234..fb97fa71480b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/IOnDoneCallback.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.proxy;
interface IOnDoneCallback {
void onDone(boolean success);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/IScreenshotProxy.aidl
index d2e3fbd65762..7b2f7e699521 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/IScreenshotProxy.aidl
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.proxy;
-import com.android.systemui.screenshot.IOnDoneCallback;
+import com.android.systemui.screenshot.proxy.IOnDoneCallback;
/** Interface implemented by ScreenshotProxyService */
interface IScreenshotProxy {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxy.kt
index e3eb3c4de80a..b44168f70ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxy.kt
@@ -24,7 +24,7 @@ package com.android.systemui.screenshot.proxy
*
* TODO: Rename and relocate 'ScreenshotProxyService' to this package and remove duplicate clients.
*/
-interface SystemUiProxy {
+interface ScreenshotProxy {
/** Indicate if the notification shade is "open"... (not in the fully collapsed position) */
suspend fun isNotificationShadeExpanded(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyClient.kt
index dcf58bde5f70..1158e2eb0ae5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyClient.kt
@@ -22,9 +22,6 @@ import android.content.Intent
import android.util.Log
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.screenshot.IOnDoneCallback
-import com.android.systemui.screenshot.IScreenshotProxy
-import com.android.systemui.screenshot.ScreenshotProxyService
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -32,8 +29,8 @@ import kotlinx.coroutines.CompletableDeferred
private const val TAG = "SystemUiProxy"
-/** An implementation of [SystemUiProxy] using [ScreenshotProxyService]. */
-class SystemUiProxyClient @Inject constructor(@Application context: Context) : SystemUiProxy {
+/** An implementation of [ScreenshotProxy] using [ScreenshotProxyService]. */
+class ScreenshotProxyClient @Inject constructor(@Application context: Context) : ScreenshotProxy {
@SuppressLint("ImplicitSamInstance")
private val proxyConnector: ServiceConnector<IScreenshotProxy> =
ServiceConnector.Impl(
@@ -41,7 +38,7 @@ class SystemUiProxyClient @Inject constructor(@Application context: Context) : S
Intent(context, ScreenshotProxyService::class.java),
Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
context.userId,
- IScreenshotProxy.Stub::asInterface
+ IScreenshotProxy.Stub::asInterface,
)
override suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyModule.kt
index 4dd5cc4b55b4..797be9121ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyModule.kt
@@ -18,14 +18,13 @@ package com.android.systemui.screenshot.proxy
import android.app.Service
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.screenshot.ScreenshotProxyService
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
-interface SystemUiProxyModule {
+interface ScreenshotProxyModule {
@Binds
@IntoMap
@@ -34,5 +33,5 @@ interface SystemUiProxyModule {
@Binds
@SysUISingleton
- fun bindSystemUiProxy(systemUiProxyClient: SystemUiProxyClient): SystemUiProxy
+ fun bindSystemUiProxy(screenshotProxyClient: ScreenshotProxyClient): ScreenshotProxy
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyService.kt
index 6df22f036273..a84f55268678 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/ScreenshotProxyService.kt
@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.screenshot
+
+package com.android.systemui.screenshot.proxy
import android.content.Intent
import android.os.IBinder
@@ -67,7 +68,7 @@ constructor(
null,
true /* dismissShade */,
true /* afterKeyguardGone */,
- true /* deferred */
+ true, /* deferred */
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index b8ea8f9052ca..4ad222d13840 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -29,6 +29,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import android.widget.ImageView
import com.android.systemui.res.R
@@ -83,6 +84,20 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
})
gestureDetector.setIsLongpressEnabled(false)
+
+ // Extend the timeout on any accessibility event (e.g. voice access or explore-by-touch).
+ setAccessibilityDelegate(
+ object : AccessibilityDelegate() {
+ override fun onRequestSendAccessibilityEvent(
+ host: ViewGroup,
+ child: View,
+ event: AccessibilityEvent,
+ ): Boolean {
+ userInteractionCallback?.invoke()
+ return super.onRequestSendAccessibilityEvent(host, child, event)
+ }
+ }
+ )
}
override fun onFinishInflate() {
@@ -147,7 +162,12 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
)
if (cutout == null) {
- screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom)
+ screenshotStatic.setPadding(
+ navBarInsets.left,
+ navBarInsets.top,
+ navBarInsets.right,
+ navBarInsets.bottom,
+ )
} else {
val waterfall = cutout.waterfallInsets
if (inPortrait) {
@@ -164,9 +184,9 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
)
} else {
screenshotStatic.setPadding(
- max(cutout.safeInsetLeft, waterfall.left),
+ max(cutout.safeInsetLeft, waterfall.left, navBarInsets.left),
waterfall.top,
- max(cutout.safeInsetRight, waterfall.right),
+ max(cutout.safeInsetRight, waterfall.right, navBarInsets.right),
max(
navBarInsets.bottom + verticalPadding,
waterfall.bottom + verticalPadding,
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 8c004c4d3adf..6844f053cd21 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -48,7 +48,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel;
import com.android.systemui.compose.ComposeInitializer;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.flags.QSComposeFragment;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -96,7 +96,7 @@ public class BrightnessDialog extends Activity {
super.onCreate(savedInstanceState);
setWindowAttributes();
View view;
- if (!QSComposeFragment.isEnabled()) {
+ if (!QsInCompose.isEnabled()) {
setContentView(R.layout.brightness_mirror_container);
view = findViewById(R.id.brightness_mirror_container);
setDialogContent((FrameLayout) view);
@@ -140,7 +140,7 @@ public class BrightnessDialog extends Activity {
window.getDecorView();
window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
- if (QSComposeFragment.isEnabled()) {
+ if (QsInCompose.isEnabled()) {
window.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
@@ -217,7 +217,7 @@ public class BrightnessDialog extends Activity {
@Override
protected void onStart() {
super.onStart();
- if (!QSComposeFragment.isEnabled()) {
+ if (!QsInCompose.isEnabled()) {
mBrightnessController.registerCallbacks();
}
MetricsLogger.visible(this, MetricsEvent.BRIGHTNESS_DIALOG);
@@ -241,7 +241,7 @@ public class BrightnessDialog extends Activity {
protected void onStop() {
super.onStop();
MetricsLogger.hidden(this, MetricsEvent.BRIGHTNESS_DIALOG);
- if (!QSComposeFragment.isEnabled()) {
+ if (!QsInCompose.isEnabled()) {
mBrightnessController.unregisterCallbacks();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 31780a56f7f0..61ac1a029f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -41,11 +41,11 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags
-import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -83,6 +83,7 @@ class GlanceableHubContainerController
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalViewModel: CommunalViewModel,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -514,7 +515,7 @@ constructor(
val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
val touchOnSmartspace =
lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
- val glanceableHubV2 = communalHubOnMobile()
+ val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
if (
!hubShowing &&
(touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 88522d559c30..3a6c250e55f1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -172,6 +172,7 @@ import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirr
import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
@@ -2269,7 +2270,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
float getDisplayDensity() {
- return mCentralSurfaces.getDisplayDensity();
+ if (ShadeWindowGoesAround.isEnabled()) {
+ return mView.getContext().getResources().getConfiguration().densityDpi;
+ } else {
+ return mCentralSurfaces.getDisplayDensity();
+ }
}
/** Return whether a touch is near the gesture handle at the bottom of screen */
@@ -3830,7 +3835,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
/* screenOnFromTouch=*/ getWakefulness().isAwakeFromTapOrGesture());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
- float displayDensity = mCentralSurfaces.getDisplayDensity();
+ float displayDensity = getDisplayDensity();
int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
int velocityDp = (int) Math.abs(vel / displayDensity);
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4d77e3ecea7b..69b3cc8cf4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -27,9 +27,10 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Build;
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;
@@ -37,10 +38,10 @@ import android.view.IWindowSession;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.Flags;
@@ -73,6 +74,7 @@ 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;
@@ -100,11 +102,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final Context mContext;
private final WindowRootViewComponent.Factory mWindowRootViewComponentFactory;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final ShadeWindowLogger mLogger;
+ private final LayoutParams mShadeWindowLayoutParams;
private final LayoutParams mLpChanged;
private final long mLockScreenDisplayTimeout;
private final float mKeyguardPreferredRefreshRate; // takes precedence over max
@@ -130,6 +133,7 @@ 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.
*
@@ -145,7 +149,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
public NotificationShadeWindowControllerImpl(
@ShadeDisplayAware Context context,
WindowRootViewComponent.Factory windowRootViewComponentFactory,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @ShadeDisplayAware WindowManager windowManager,
IActivityManager activityManager,
DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
@@ -163,14 +167,17 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
NotificationShadeWindowModel notificationShadeWindowModel,
- Lazy<CommunalInteractor> communalInteractor) {
+ SecureSettings secureSettings,
+ Lazy<CommunalInteractor> communalInteractor,
+ @ShadeDisplayAware LayoutParams shadeWindowLayoutParams) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mActivityManager = activityManager;
mDozeParameters = dozeParameters;
mKeyguardStateController = keyguardStateController;
mLogger = logger;
+ mShadeWindowLayoutParams = shadeWindowLayoutParams;
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
mLpChanged = new LayoutParams();
mKeyguardViewMediator = keyguardViewMediator;
@@ -178,6 +185,7 @@ 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;
@@ -266,7 +274,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
// Now that the notification shade encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
- mLp = ShadeWindowLayoutParams.INSTANCE.create(mContext);
+ // mLP is assigned here (instead of the constructor) as its null value is also used to check
+ // if the shade window has been attached.
+ mLp = mShadeWindowLayoutParams;
mWindowManager.addView(mWindowRootView, mLp);
// We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
@@ -400,7 +410,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
(long) mLpChanged.preferredMaxDisplayRefreshRate);
}
- if (state.bouncerShowing && !isDebuggable()) {
+ if (state.bouncerShowing && !isSecureWindowsDisabled()) {
mLpChanged.flags |= LayoutParams.FLAG_SECURE;
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -413,8 +423,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- protected boolean isDebuggable() {
- return Build.IS_DEBUGGABLE;
+ private boolean isSecureWindowsDisabled() {
+ return mSecureSettings.getIntForUser(
+ Settings.Secure.DISABLE_SECURE_WINDOWS,
+ 0,
+ UserHandle.USER_CURRENT) == 1;
}
private void adjustScreenOrientation(NotificationShadeWindowState state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 91ca2ca52287..ff39a3ddc17c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams
import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
@@ -80,6 +81,13 @@ object ShadeDisplayAwareModule {
@Provides
@ShadeDisplayAware
@SysUISingleton
+ fun provideShadeWindowLayoutParams(@ShadeDisplayAware context: Context): LayoutParams {
+ return ShadeWindowLayoutParams.create(context)
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
fun provideShadeWindowManager(
defaultWindowManager: WindowManager,
@ShadeDisplayAware context: Context,
@@ -195,9 +203,9 @@ object ShadeDisplayAwareModule {
@Provides
@IntoMap
@ClassKey(ShadePrimaryDisplayCommand::class)
- fun provideShadePrimaryDisplayCommand(impl: ShadePrimaryDisplayCommand): CoreStartable {
+ fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable {
return if (ShadeWindowGoesAround.isEnabled) {
- impl
+ impl.get()
} else {
CoreStartable.NOP
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 15b22700072f..8937ce33cd38 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -33,7 +33,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
@@ -91,7 +90,6 @@ abstract class ShadeViewProviderModule {
layoutInsetController: NotificationInsetsController,
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
qsSceneAdapter: Provider<QSSceneAdapter>,
- alternateBouncerDependencies: Provider<AlternateBouncerDependencies>,
): WindowRootView {
return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
@@ -107,7 +105,6 @@ abstract class ShadeViewProviderModule {
layoutInsetController = layoutInsetController,
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
qsSceneAdapter = qsSceneAdapter,
- alternateBouncerDependencies = alternateBouncerDependencies.get(),
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index 1b22ee40009b..bb96b0b3ce50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -31,9 +31,12 @@ interface ShadeDisplayPolicy {
@Module
interface ShadeDisplayPolicyModule {
+
+ @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
+
@IntoSet
@Binds
- fun provideDefaultPolicyToSet(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+ fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
@IntoSet
@Binds
@@ -41,5 +44,9 @@ interface ShadeDisplayPolicyModule {
impl: AnyExternalShadeDisplayPolicy
): ShadeDisplayPolicy
- @Binds fun provideDefaultPolicy(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+ @Binds
+ @IntoSet
+ fun provideStatusBarTouchShadeDisplayPolicy(
+ impl: StatusBarTouchShadeDisplayPolicy
+ ): ShadeDisplayPolicy
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
index 13e766409bab..d43aad70368e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
@@ -22,12 +22,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/** Policy to specify a display id explicitly. */
-open class SpecificDisplayIdPolicy(displayId: Int) : ShadeDisplayPolicy {
- override val name: String
- get() = "display_${displayId}_policy"
+open class SpecificDisplayIdPolicy(id: Int) : ShadeDisplayPolicy {
+ override val name: String = "display_${id}_policy"
- override val displayId: StateFlow<Int> = MutableStateFlow(displayId)
+ override val displayId: StateFlow<Int> = MutableStateFlow(id)
}
-class DefaultShadeDisplayPolicy @Inject constructor() :
+class DefaultDisplayShadePolicy @Inject constructor() :
SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
new file mode 100644
index 000000000000..22e9487af84c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.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.shade.display
+
+import android.util.Log
+import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced
+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.shade.shared.flag.ShadeWindowGoesAround
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/**
+ * Moves the shade on the last display that received a status bar touch.
+ *
+ * If the display is removed, falls back to the default one.
+ */
+@SysUISingleton
+class StatusBarTouchShadeDisplayPolicy
+@Inject
+constructor(displayRepository: DisplayRepository, @Background val backgroundScope: CoroutineScope) :
+ ShadeDisplayPolicy {
+ override val name: String
+ get() = "status_bar_latest_touch"
+
+ private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+ private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
+
+ override val displayId: StateFlow<Int>
+ get() = currentDisplayId
+
+ private var removalListener: Job? = null
+
+ /** Called when the status bar on the given display is touched. */
+ fun onStatusBarTouched(statusBarDisplayId: Int) {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ if (statusBarDisplayId !in availableDisplayIds.value) {
+ Log.e(TAG, "Got touch on unknown display $statusBarDisplayId")
+ return
+ }
+ currentDisplayId.value = statusBarDisplayId
+ if (removalListener == null) {
+ // Lazy start this at the first invocation. it's fine to let it run also when the policy
+ // is not selected anymore, as the job doesn't do anything until someone subscribes to
+ // displayId.
+ removalListener = monitorDisplayRemovals()
+ }
+ }
+
+ private fun monitorDisplayRemovals(): Job {
+ return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") {
+ currentDisplayId.subscriptionCount
+ .map { it > 0 }
+ .distinctUntilChanged()
+ // When Active is false, no collect happens, and the old one is cancelled.
+ // This is needed to prevent "availableDisplayIds" collection while nobody is
+ // listening at the flow provided by this class.
+ .collectLatest { active ->
+ if (active) {
+ availableDisplayIds.collect { availableIds ->
+ if (currentDisplayId.value !in availableIds) {
+ currentDisplayId.value = Display.DEFAULT_DISPLAY
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "StatusBarTouchDisplayPolicy"
+ }
+}
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 fb2cbec84236..34148671cf2a 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
@@ -19,6 +19,7 @@ package com.android.systemui.shade.domain.interactor
import android.content.Context
import android.util.Log
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams
import androidx.annotation.UiThread
import com.android.app.tracing.coroutines.launchTraced
import com.android.app.tracing.traceSection
@@ -28,7 +29,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.ShadeWindowLayoutParams
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.util.kotlin.getOrNull
@@ -46,14 +46,12 @@ constructor(
optionalShadeRootView: Optional<WindowRootView>,
private val shadePositionRepository: ShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: Context,
+ @ShadeDisplayAware private val shadeLayoutParams: LayoutParams,
@ShadeDisplayAware private val wm: WindowManager,
@Background private val bgScope: CoroutineScope,
@Main private val mainThreadContext: CoroutineContext,
) : CoreStartable {
- private val shadeLayoutParams: WindowManager.LayoutParams =
- ShadeWindowLayoutParams.create(shadeContext)
-
private val shadeRootView =
optionalShadeRootView.getOrNull()
?: error(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index dd1b58c47b63..c6752f867183 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -30,8 +30,8 @@ fun singleShadeActions(
isDownFromTopEdgeEnabled: Boolean = true,
requireTwoPointersForTopEdgeForQs: Boolean = false,
): Array<Pair<UserAction, UserActionResult>> {
- val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
- val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
+ val shadeUserActionResult = UserActionResult(Scenes.Shade)
+ val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings)
return buildList {
// Swiping down, not from the edge, always goes to shade.
add(Swipe.Down to shadeUserActionResult)
@@ -53,7 +53,7 @@ fun singleShadeActions(
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
+ val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade)
return arrayOf(
// Swiping down, not from the edge, always goes to shade.
Swipe.Down to shadeUserActionResult,
@@ -66,10 +66,8 @@ fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val notifShadeUserActionResult =
- UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
- val qsShadeuserActionResult =
- UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
+ val notifShadeUserActionResult = UserActionResult.ShowOverlay(Overlays.NotificationsShade)
+ val qsShadeuserActionResult = UserActionResult.ShowOverlay(Overlays.QuickSettingsShade)
return arrayOf(
Swipe.Down to notifShadeUserActionResult,
Swipe.Down(fromSource = SceneContainerEdge.TopRight) to qsShadeuserActionResult,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index bb0467f10e16..2eae3eb4fc19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -23,6 +23,8 @@ import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,17 +39,30 @@ class CallChipInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ ongoingCallInteractor: OngoingCallInteractor,
repository: OngoingCallRepository,
@StatusBarChipsLog private val logger: LogBuffer,
) {
val ongoingCallState: StateFlow<OngoingCallModel> =
- repository.ongoingCallState
+ (if (StatusBarChipsModernization.isEnabled)
+ ongoingCallInteractor.ongoingCallState
+ else
+ repository.ongoingCallState)
.onEach {
- logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = it::class.simpleName },
+ { "State: $str1" }
+ )
}
- .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
+ .stateIn(
+ scope,
+ SharingStarted.Lazily,
+ OngoingCallModel.NoCall
+ )
companion object {
private val TAG = "OngoingCall".pad()
}
-}
+} \ No newline at end of file
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 b8cdd2587774..85b50d3320dd 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
@@ -35,6 +35,7 @@ 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.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -59,16 +60,25 @@ constructor(
interactor.ongoingCallState
.map { state ->
when (state) {
- is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
+ is OngoingCallModel.NoCall,
+ is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
is OngoingCallModel.InCall -> {
val icon =
if (
Flags.statusBarCallChipNotificationIcon() &&
state.notificationIconView != null
) {
+ StatusBarConnectedDisplays.assertInLegacyMode()
OngoingActivityChipModel.ChipIcon.StatusBarView(
state.notificationIconView
)
+ } else if (
+ StatusBarConnectedDisplays.isEnabled &&
+ Flags.statusBarCallChipNotificationIcon()
+ ) {
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+ state.notificationKey
+ )
} else {
OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index b3dbf299e7cc..229cef910c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor
+import android.media.projection.StopReason
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
@@ -65,7 +66,9 @@ constructor(
/** Stops the currently active MediaRouter cast. */
fun stopCasting() {
- activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
+ activeCastDevice.value?.let {
+ mediaRouterRepository.stopCasting(it, StopReason.STOP_PRIVACY_CHIP)
+ }
}
companion object {
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 087b51032fcf..571a3e44d233 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
@@ -22,6 +22,7 @@ import com.android.systemui.log.core.Logger
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -100,12 +101,17 @@ constructor(
private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
val statusBarChipIconView = this.statusBarChipIconView
if (statusBarChipIconView == null) {
- logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
- str1 = extraLogTag
+ if (!StatusBarConnectedDisplays.isEnabled) {
+ logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
+ str1 = extraLogTag
+ }
+ // When the flag is disabled, we keep the old behavior of returning null.
+ // When the flag is enabled, the icon will always be null, and will later be
+ // fetched in the UI layer using the notification key.
+ return null
}
- return null
}
- return NotificationChipModel(key, statusBarChipIconView)
+ return NotificationChipModel(key, statusBarChipIconView, whenTime)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 5698ee6d1917..4588b19bd720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -19,4 +19,8 @@ package com.android.systemui.statusbar.chips.notification.domain.model
import com.android.systemui.statusbar.StatusBarIconView
/** Modeling all the data needed to render a status bar notification chip. */
-data class NotificationChipModel(val key: String, val statusBarChipIconView: StatusBarIconView)
+data class NotificationChipModel(
+ val key: String,
+ val statusBarChipIconView: StatusBarIconView?,
+ val whenTime: Long,
+)
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 9eff627c8714..2cd5bb339072 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
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.chips.notification.domain.model.Notificati
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -50,7 +51,14 @@ constructor(
/** Converts the notification to the [OngoingActivityChipModel] object. */
private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
- val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+ val icon =
+ if (this.statusBarChipIconView != null) {
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+ } else {
+ StatusBarConnectedDisplays.assertInNewMode()
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
+ }
// TODO(b/364653005): Use the notification color if applicable.
val colors = ColorsModel.Themed
val onClickListener =
@@ -63,9 +71,13 @@ constructor(
)
}
}
- return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ return OngoingActivityChipModel.Shown.ShortTimeDelta(
+ icon,
+ colors,
+ time = this.whenTime,
+ onClickListener,
+ )
// TODO(b/364653005): Use Notification.showWhen to determine if we should show the time.
- // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text.
// TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`.
// TODO(b/364653005): If the app that posted the notification is in the foreground, don't
// show that app's chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index f5952f4804fc..0b5e669b5fca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
+import android.media.projection.StopReason
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -140,7 +141,7 @@ constructor(
/** Stops the recording. */
fun stopRecording() {
- scope.launch { screenRecordRepository.stopRecording() }
+ scope.launch { screenRecordRepository.stopRecording(StopReason.STOP_PRIVACY_CHIP) }
}
companion object {
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 f4462a434477..cf69d401df60 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
@@ -32,11 +32,13 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
/** Binder for ongoing activity chip views. */
object OngoingActivityChipBinder {
/** Binds the given [chipModel] data to the given [chipView]. */
- fun bind(chipModel: OngoingActivityChipModel, chipView: View) {
+ fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) {
val chipContext = chipView.context
val chipDefaultIconView: ImageView =
chipView.requireViewById(R.id.ongoing_activity_chip_icon)
@@ -51,7 +53,7 @@ object OngoingActivityChipBinder {
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
- setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView)
+ setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
chipView.setOnClickListener(chipModel.onClickListener)
updateChipPadding(
@@ -85,6 +87,7 @@ object OngoingActivityChipBinder {
chipModel: OngoingActivityChipModel.Shown,
backgroundView: ChipBackgroundContainer,
defaultIconView: ImageView,
+ iconViewStore: IconViewStore?,
) {
// Always remove any previously set custom icon. If we have a new custom icon, we'll re-add
// it.
@@ -108,38 +111,62 @@ object OngoingActivityChipBinder {
defaultIconView.untintView()
}
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
- // Hide the default icon since we'll show this custom icon instead.
- defaultIconView.visibility = View.GONE
-
- // Add the new custom icon:
- // 1. Set up the right visual params.
- val iconView = icon.impl
- with(iconView) {
- id = CUSTOM_ICON_VIEW_ID
- // TODO(b/354930838): Update the content description to not include "phone" and
- // maybe include the app name.
- contentDescription =
- context.resources.getString(R.string.ongoing_phone_call_content_description)
- tintView(iconTint)
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView)
+ }
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
+ StatusBarConnectedDisplays.assertInNewMode()
+ val iconView = fetchStatusBarIconView(iconViewStore, icon)
+ if (iconView == null) {
+ // This means that the notification key doesn't exist anymore.
+ return
}
+ setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
+ }
+ }
+ }
- // 2. If we just reinflated the view, we may need to detach the icon view from the
- // old chip before we reattach it to the new one.
- // See also: NotificationIconContainerViewBinder#bindIcons.
- val currentParent = iconView.parent as? ViewGroup
- if (currentParent != null && currentParent != backgroundView) {
- currentParent.removeView(iconView)
- currentParent.removeTransientView(iconView)
- }
+ private fun fetchStatusBarIconView(
+ iconViewStore: IconViewStore?,
+ icon: OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon,
+ ): StatusBarIconView? {
+ StatusBarConnectedDisplays.assertInNewMode()
+ if (iconViewStore == null) {
+ throw IllegalStateException("Store should always be non-null when flag is enabled.")
+ }
+ return iconViewStore.iconView(icon.notificationKey)
+ }
- // 3: Add the icon as the starting view.
- backgroundView.addView(
- iconView,
- /* index= */ 0,
- generateCustomIconLayoutParams(iconView),
- )
- }
+ private fun setStatusBarIconView(
+ defaultIconView: ImageView,
+ iconView: StatusBarIconView,
+ iconTint: Int,
+ backgroundView: ChipBackgroundContainer,
+ ) {
+ // Hide the default icon since we'll show this custom icon instead.
+ defaultIconView.visibility = View.GONE
+
+ // 1. Set up the right visual params.
+ with(iconView) {
+ id = CUSTOM_ICON_VIEW_ID
+ // TODO(b/354930838): Update the content description to not include "phone" and maybe
+ // include the app name.
+ contentDescription =
+ context.resources.getString(R.string.ongoing_phone_call_content_description)
+ tintView(iconTint)
+ }
+
+ // 2. If we just reinflated the view, we may need to detach the icon view from the old chip
+ // before we reattach it to the new one.
+ // See also: NotificationIconContainerViewBinder#bindIcons.
+ val currentParent = iconView.parent as? ViewGroup
+ if (currentParent != null && currentParent != backgroundView) {
+ currentParent.removeView(iconView)
+ currentParent.removeTransientView(iconView)
}
+
+ // 3: Add the icon as the starting view.
+ backgroundView.addView(iconView, /* index= */ 0, generateCustomIconLayoutParams(iconView))
}
private fun View.getCustomIconView(): StatusBarIconView? {
@@ -192,10 +219,14 @@ object OngoingActivityChipBinder {
}
is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
chipShortTimeDeltaView.setTime(chipModel.time)
- // TODO(b/364653005): DateTimeView's relative time doesn't quite match the format we
- // want in the status bar chips.
- chipShortTimeDeltaView.isShowRelativeTime = true
chipShortTimeDeltaView.visibility = View.VISIBLE
+ chipShortTimeDeltaView.isShowRelativeTime = true
+ chipShortTimeDeltaView.setRelativeTimeDisambiguationTextMask(
+ DateTimeView.DISAMBIGUATION_TEXT_PAST
+ )
+ chipShortTimeDeltaView.setRelativeTimeUnitDisplayLength(
+ DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM
+ )
chipTextView.visibility = View.GONE
chipTimeView.hide()
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 cf07af1fc5dd..2dce4e38b803 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
@@ -21,6 +21,7 @@ import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
/** Model representing the display of an ongoing activity as a chip in the status bar. */
sealed class OngoingActivityChipModel {
@@ -132,6 +133,17 @@ sealed class OngoingActivityChipModel {
"OngoingActivityChipModel.ChipIcon.StatusBarView created even though " +
"Flags.statusBarCallChipNotificationIcon is not enabled"
}
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ }
+ }
+
+ /**
+ * The icon is a custom icon, which is set on a notification, and can be looked up using the
+ * provided [notificationKey]. The icon was likely created by an external app.
+ */
+ data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon {
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 52a79d39bb3b..0de4ac028d11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -132,13 +132,10 @@ public class AccessPointControllerImpl implements AccessPointController,
}
if (mWifiPickerTracker != null) {
- mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ mWifiPickerTracker.onStop();
}
Context context = mContext.createContextAsUser(UserHandle.of(newUserId), /* flags= */ 0);
mWifiPickerTracker = mWifiPickerTrackerFactory.create(context, mLifecycle, this, TAG);
- if (!mCallbacks.isEmpty()) {
- mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
- }
}
@Override
@@ -147,7 +144,11 @@ public class AccessPointControllerImpl implements AccessPointController,
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
if (mCallbacks.size() == 1) {
- mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+ if (mWifiPickerTrackerFactory.isSupported()) {
+ mWifiPickerTracker.onStart();
+ } else {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+ }
}
}
@@ -157,7 +158,11 @@ public class AccessPointControllerImpl implements AccessPointController,
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
if (mCallbacks.isEmpty()) {
- mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ if (mWifiPickerTrackerFactory.isSupported()) {
+ mWifiPickerTracker.onStop();
+ } else {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index c37b01fff578..a9c278490a0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@ internal class MobileState(
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false)
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
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 614f0f48d1fc..d24eddaf321f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -20,6 +20,7 @@ import android.app.StatusBarManager
import android.content.Context
import android.os.Binder
import android.os.RemoteException
+import android.view.Display
import android.view.WindowInsets
import com.android.internal.statusbar.IStatusBarService
import com.android.internal.statusbar.RegisterStatusBarResult
@@ -47,20 +48,32 @@ constructor(
override fun start() {
StatusBarConnectedDisplays.assertInNewMode()
- val result: RegisterStatusBarResult =
+ val resultPerDisplay: Map<String, RegisterStatusBarResult> =
try {
- barService.registerStatusBar(commandQueue)
+ barService.registerStatusBarForAllDisplays(commandQueue)
} catch (ex: RemoteException) {
ex.rethrowFromSystemServer()
return
}
- createNavigationBar(result)
+ resultPerDisplay[Display.DEFAULT_DISPLAY.toString()]?.let {
+ createNavigationBar(it)
+ // Set up the initial icon state
+ val numIcons: Int = it.mIcons.size
+ for (i in 0 until numIcons) {
+ commandQueue.setIcon(it.mIcons.keyAt(i), it.mIcons.valueAt(i))
+ }
+ }
+ for ((displayId, result) in resultPerDisplay.entries) {
+ initializeStatusBarForDisplay(displayId.toInt(), result)
+ }
+ }
+
+ private fun initializeStatusBarForDisplay(displayId: Int, result: RegisterStatusBarResult) {
if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
- statusBarModeRepository.defaultDisplay.showTransient()
+ statusBarModeRepository.forDisplay(displayId).showTransient()
}
- val displayId = context.display.displayId
val commandQueueCallbacks = commandQueueCallbacksLazy.get()
commandQueueCallbacks.onSystemBarAttributesChanged(
displayId,
@@ -81,12 +94,6 @@ constructor(
result.mShowImeSwitcher,
)
- // Set up the initial icon state
- val numIcons: Int = result.mIcons.size
- for (i in 0 until numIcons) {
- commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
- }
-
// set the initial view visibility
val disabledFlags1 = result.mDisabledFlags1
val disabledFlags2 = result.mDisabledFlags2
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 84c7ab200fad..9e9a38e87924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -26,6 +26,7 @@ import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.phone.AutoHideControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
import com.android.systemui.util.kotlin.pairwiseBy
@@ -50,6 +51,7 @@ constructor(
private val initializerStore: StatusBarInitializerStore,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
private val statusBarInitializerStore: StatusBarInitializerStore,
+ private val autoHideControllerStore: AutoHideControllerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
) : CoreStartable {
@@ -95,6 +97,7 @@ constructor(
statusBarModeRepositoryStore.forDisplay(displayId),
initializerStore.forDisplay(displayId),
statusBarWindowControllerStore.forDisplay(displayId),
+ autoHideControllerStore.forDisplay(displayId),
)
.start()
}
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 ff4760fd2837..f91c5dd1b3ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -71,9 +71,9 @@ constructor(
@Assisted private val statusBarInitializer: StatusBarInitializer,
@Assisted private val statusBarWindowController: StatusBarWindowController,
@Main private val mainContext: CoroutineContext,
+ @Assisted private val autoHideController: AutoHideController,
private val demoModeController: DemoModeController,
private val pluginDependencyProvider: PluginDependencyProvider,
- private val autoHideController: AutoHideController,
private val remoteInputManager: NotificationRemoteInputManager,
private val notificationShadeWindowViewControllerLazy:
Lazy<NotificationShadeWindowViewController>,
@@ -210,10 +210,6 @@ constructor(
}
private fun setUpAutoHide() {
- if (displayId != Display.DEFAULT_DISPLAY) {
- return
- }
- // TODO(b/373309973): per display implementation of auto hide controller
autoHideController.setStatusBar(
object : AutoHideUiElement {
override fun synchronizeState() {}
@@ -241,18 +237,15 @@ constructor(
if (!demoModeController.isInDemoMode) {
barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
}
- if (displayId == Display.DEFAULT_DISPLAY) {
- // TODO(b/373309973): per display implementation of auto hide controller
- autoHideController.touchAutoHide()
- }
+ autoHideController.touchAutoHide()
}
private fun updateBubblesVisibility(statusBarVisible: Boolean) {
if (displayId != Display.DEFAULT_DISPLAY) {
+ // Bubbles are currently only supported on the default display.
return
}
bubblesOptional.ifPresent { bubbles: Bubbles ->
- // TODO(b/373311537): per display implementation of Bubbles
bubbles.onStatusBarVisibilityChanged(statusBarVisible)
}
}
@@ -288,6 +281,7 @@ constructor(
statusBarModeRepository: StatusBarModePerDisplayRepository,
statusBarInitializer: StatusBarInitializer,
statusBarWindowController: StatusBarWindowController,
+ autoHideController: AutoHideController,
): StatusBarOrchestrator
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 47b695e50a0e..2588c7ae2363 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
@@ -63,11 +64,6 @@ interface StatusBarModule {
@Binds
@IntoMap
- @ClassKey(OngoingCallController::class)
- fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
-
- @Binds
- @IntoMap
@ClassKey(LightBarController::class)
fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
@@ -90,6 +86,18 @@ interface StatusBarModule {
): LightBarController.Factory
companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(OngoingCallController::class)
+ fun ongoingCallController(
+ controller: OngoingCallController
+ ): CoreStartable =
+ if (StatusBarChipsModernization.isEnabled) {
+ CoreStartable.NOP
+ } else {
+ controller
+ }
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 24088d2dabfa..11ec2edb36f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -30,6 +30,7 @@ import androidx.annotation.Nullable;
import com.android.internal.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.DeviceConfigProxy;
@@ -72,7 +73,7 @@ public class AssistantFeedbackController {
/** Injected constructor */
@Inject
public AssistantFeedbackController(@Main Handler handler,
- Context context, DeviceConfigProxy proxy) {
+ @Application Context context, DeviceConfigProxy proxy) {
mHandler = handler;
mContext = context;
mDeviceConfigProxy = proxy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 60d846ebacac..2d6ed70eb905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -55,6 +55,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.NotificationChannels;
@@ -84,7 +85,7 @@ public class InstantAppNotifier
@Inject
public InstantAppNotifier(
- Context context,
+ @ShadeDisplayAware Context context,
CommandQueue commandQueue,
UserTracker userTracker,
@Main Executor mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index b67092ca9348..91864c226964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -17,98 +17,17 @@
package com.android.systemui.statusbar.notification
import android.content.Context
-import android.provider.DeviceConfig
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
-import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
-import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.PriorityBucket
-import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.Utils
import javax.inject.Inject
-private var sUsePeopleFiltering: Boolean? = null
-
-/** Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. */
@SysUISingleton
class NotificationSectionsFeatureManager
@Inject
-constructor(val proxy: DeviceConfigProxy, val context: Context) {
-
- fun isFilteringEnabled(): Boolean {
- return usePeopleFiltering(proxy)
- }
+constructor(@ShadeDisplayAware val context: Context) {
fun isMediaControlsEnabled(): Boolean {
return Utils.useQsMediaPlayer(context)
}
-
- fun getNotificationBuckets(): IntArray {
- if (
- PriorityPeopleSection.isEnabled ||
- NotificationMinimalism.isEnabled ||
- NotificationClassificationFlag.isEnabled
- ) {
- // We don't need this list to be adaptive, it can be the superset of all features.
- return PriorityBucket.getAllInOrder()
- }
- return when {
- isFilteringEnabled() && isMediaControlsEnabled() ->
- intArrayOf(
- BUCKET_HEADS_UP,
- BUCKET_FOREGROUND_SERVICE,
- BUCKET_MEDIA_CONTROLS,
- BUCKET_PEOPLE,
- BUCKET_ALERTING,
- BUCKET_SILENT
- )
- !isFilteringEnabled() && isMediaControlsEnabled() ->
- intArrayOf(
- BUCKET_HEADS_UP,
- BUCKET_FOREGROUND_SERVICE,
- BUCKET_MEDIA_CONTROLS,
- BUCKET_ALERTING,
- BUCKET_SILENT
- )
- isFilteringEnabled() && !isMediaControlsEnabled() ->
- intArrayOf(
- BUCKET_HEADS_UP,
- BUCKET_FOREGROUND_SERVICE,
- BUCKET_PEOPLE,
- BUCKET_ALERTING,
- BUCKET_SILENT
- )
- else -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
- }
- }
-
- fun getNumberOfBuckets(): Int {
- return getNotificationBuckets().size
- }
-
- @VisibleForTesting
- fun clearCache() {
- sUsePeopleFiltering = null
- }
-}
-
-private fun usePeopleFiltering(proxy: DeviceConfigProxy): Boolean {
- if (sUsePeopleFiltering == null) {
- sUsePeopleFiltering =
- proxy.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- NOTIFICATIONS_USE_PEOPLE_FILTERING,
- true
- )
- }
-
- return sUsePeopleFiltering!!
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 3b48b3922dc5..d0d22582a4f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -4,7 +4,6 @@ import android.util.FloatProperty
import android.view.View
import androidx.annotation.FloatRange
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import kotlin.math.abs
@@ -39,15 +38,11 @@ interface Roundable {
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
val topCornerRadius: Float
- get() =
- if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius
- else topRoundness * maxRadius
+ get() = roundableState.topCornerRadius
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
val bottomCornerRadius: Float
- get() =
- if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius
- else bottomRoundness * maxRadius
+ get() = roundableState.bottomCornerRadius
/** Get and update the current radii */
val updatedRadii: FloatArray
@@ -123,7 +118,7 @@ interface Roundable {
return requestTopRoundness(
value = value,
sourceType = sourceType,
- animate = roundableState.targetView.isShown
+ animate = roundableState.targetView.isShown,
)
}
@@ -190,7 +185,7 @@ interface Roundable {
return requestBottomRoundness(
value = value,
sourceType = sourceType,
- animate = roundableState.targetView.isShown
+ animate = roundableState.targetView.isShown,
)
}
@@ -289,11 +284,7 @@ interface Roundable {
*
* This method reuses the previous [radii] for performance reasons.
*/
- fun updateRadii(
- topCornerRadius: Float,
- bottomCornerRadius: Float,
- radii: FloatArray,
- ) {
+ fun updateRadii(topCornerRadius: Float, bottomCornerRadius: Float, radii: FloatArray) {
if (radii.size != 8) error("Unexpected radiiBuffer size ${radii.size}")
if (radii[0] != topCornerRadius || radii[4] != bottomCornerRadius) {
@@ -312,11 +303,7 @@ interface Roundable {
*/
class RoundableState
@JvmOverloads
-constructor(
- internal val targetView: View,
- private val roundable: Roundable,
- maxRadius: Float,
-) {
+constructor(internal val targetView: View, private val roundable: Roundable, maxRadius: Float) {
internal var maxRadius = maxRadius
private set
@@ -387,18 +374,12 @@ constructor(
internal fun isBottomAnimating() = PropertyAnimator.isAnimating(targetView, bottomAnimatable)
/** Set the current top roundness */
- internal fun setTopRoundness(
- value: Float,
- animated: Boolean,
- ) {
+ internal fun setTopRoundness(value: Float, animated: Boolean) {
PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
}
/** Set the current bottom roundness */
- internal fun setBottomRoundness(
- value: Float,
- animated: Boolean,
- ) {
+ internal fun setBottomRoundness(value: Float, animated: Boolean) {
PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index 0c49713131e8..0f08ae407d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -23,35 +23,37 @@ import android.content.pm.PackageManager
import android.service.notification.StatusBarNotification
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.phone.CentralSurfaces
import javax.inject.Inject
@SysUISingleton
-class TargetSdkResolver @Inject constructor(
- private val context: Context
-) {
+class TargetSdkResolver @Inject constructor(@Application private val context: Context) {
fun initialize(collection: CommonNotifCollection) {
- collection.addCollectionListener(object : NotifCollectionListener {
- override fun onEntryBind(entry: NotificationEntry, sbn: StatusBarNotification) {
- entry.targetSdk = resolveNotificationSdk(sbn)
+ collection.addCollectionListener(
+ object : NotifCollectionListener {
+ override fun onEntryBind(entry: NotificationEntry, sbn: StatusBarNotification) {
+ entry.targetSdk = resolveNotificationSdk(sbn)
+ }
}
- })
+ )
}
private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
- val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+ val applicationInfo =
+ getApplicationInfoFromExtras(sbn.notification)
?: getApplicationInfoFromPackageManager(sbn)
return applicationInfo?.targetSdkVersion ?: 0
}
private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
- notification.extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
- )
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java,
+ )
private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index e75c11de57c7..3c31d893cf72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.collection.provider.VisualSta
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.BooleanFlowOperators;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -78,6 +79,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private final CommunalSceneInteractor mCommunalSceneInteractor;
private final ShadeInteractor mShadeInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final KeyguardStateController mKeyguardStateController;
private final VisualStabilityCoordinatorLogger mLogger;
private boolean mSleepy = true;
@@ -120,6 +122,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
CommunalSceneInteractor communalSceneInteractor,
ShadeInteractor shadeInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ KeyguardStateController keyguardStateController,
VisualStabilityCoordinatorLogger logger) {
mHeadsUpManager = headsUpManager;
mShadeAnimationInteractor = shadeAnimationInteractor;
@@ -133,6 +136,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
mCommunalSceneInteractor = communalSceneInteractor;
mShadeInteractor = shadeInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardStateController = keyguardStateController;
mLogger = logger;
dumpManager.registerDumpable(this);
@@ -162,17 +166,29 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
KeyguardState.LOCKSCREEN),
this::onLockscreenKeyguardStateTransitionValueChanged);
}
+
if (Flags.checkLockscreenGoneTransition()) {
- mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
- Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
- Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
- this::onLockscreenInGoneTransitionChanged);
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+ Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone), null),
+ this::onLockscreenInGoneTransitionChanged);
+ } else {
+ mKeyguardStateController.addCallback(mKeyguardFadeAwayAnimationCallback);
+ }
}
-
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
+ final KeyguardStateController.Callback mKeyguardFadeAwayAnimationCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ onLockscreenInGoneTransitionChanged(
+ mKeyguardStateController.isKeyguardFadingAway());
+ }
+ };
+
// TODO(b/203826051): Ensure stability manager can allow reordering off-screen
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 6b70a0807b86..03b38f9a8ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.notification.collection.provider
import android.content.Context
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
@@ -31,9 +32,7 @@ import javax.inject.Inject
* visibility when it invalidates, and we just store that state here.)
*/
@SysUISingleton
-class SectionHeaderVisibilityProvider @Inject constructor(
- context: Context
-) {
+class SectionHeaderVisibilityProvider @Inject constructor(@ShadeDisplayAware context: Context) {
val neverShowSectionHeaders =
context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
var sectionHeadersVisible = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index 70fabc0587d4..c731ac648a77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -19,15 +19,16 @@ package com.android.systemui.statusbar.notification.collection.render
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import javax.inject.Inject
@SysUISingleton
-class MediaContainerController @Inject constructor(
- private val layoutInflater: LayoutInflater
-) : NodeController {
+class MediaContainerController
+@Inject
+constructor(@ShadeDisplayAware private val layoutInflater: LayoutInflater) : NodeController {
override val nodeLabel = "MediaContainer"
var mediaContainerView: MediaContainerView? = null
@@ -42,11 +43,12 @@ class MediaContainerController @Inject constructor(
parent.removeView(_view)
}
}
- val inflated = layoutInflater.inflate(
+ val inflated =
+ layoutInflater.inflate(
R.layout.keyguard_media_container,
parent,
- false /* attachToRoot */)
- as MediaContainerView
+ false, /* attachToRoot */
+ ) as MediaContainerView
if (oldPos != -1) {
parent.addView(inflated, oldPos)
}
@@ -57,6 +59,8 @@ class MediaContainerController @Inject constructor(
get() = mediaContainerView!!
override fun offerToKeepInParentForAnimation(): Boolean = false
+
override fun removeFromParentIfKeptForAnimation(): Boolean = false
+
override fun resetKeepInParentForAnimation() {}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 5464d08fca67..37005c1644e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -21,8 +21,9 @@ import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import com.android.systemui.res.R
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.dagger.HeaderClickAction
import com.android.systemui.statusbar.notification.dagger.HeaderText
import com.android.systemui.statusbar.notification.dagger.NodeLabel
@@ -32,30 +33,37 @@ import javax.inject.Inject
interface SectionHeaderController {
fun reinflateView(parent: ViewGroup)
+
val headerView: SectionHeaderView?
+
fun setClearSectionEnabled(enabled: Boolean)
+
fun setOnClearSectionClickListener(listener: View.OnClickListener)
}
@SectionHeaderScope
-class SectionHeaderNodeControllerImpl @Inject constructor(
+class SectionHeaderNodeControllerImpl
+@Inject
+constructor(
@NodeLabel override val nodeLabel: String,
- private val layoutInflater: LayoutInflater,
+ @ShadeDisplayAware private val layoutInflater: LayoutInflater,
@HeaderText @StringRes private val headerTextResId: Int,
private val activityStarter: ActivityStarter,
- @HeaderClickAction private val clickIntentAction: String
+ @HeaderClickAction private val clickIntentAction: String,
) : NodeController, SectionHeaderController {
private var _view: SectionHeaderView? = null
private var clearAllButtonEnabled = false
private var clearAllClickListener: View.OnClickListener? = null
- private val onHeaderClickListener = View.OnClickListener {
- activityStarter.startActivity(
+ private val onHeaderClickListener =
+ View.OnClickListener {
+ activityStarter.startActivity(
Intent(clickIntentAction),
true /* onlyProvisioned */,
true /* dismissShade */,
- Intent.FLAG_ACTIVITY_SINGLE_TOP)
- }
+ Intent.FLAG_ACTIVITY_SINGLE_TOP,
+ )
+ }
override fun reinflateView(parent: ViewGroup) {
var oldPos = -1
@@ -66,11 +74,12 @@ class SectionHeaderNodeControllerImpl @Inject constructor(
parent.removeView(_view)
}
}
- val inflated = layoutInflater.inflate(
+ val inflated =
+ layoutInflater.inflate(
R.layout.status_bar_notification_section_header,
parent,
- false /* attachToRoot */)
- as SectionHeaderView
+ false, /* attachToRoot */
+ ) as SectionHeaderView
inflated.setHeaderText(headerTextResId)
inflated.setOnHeaderClickListener(onHeaderClickListener)
clearAllClickListener?.let { inflated.setOnClearAllClickListener(it) }
@@ -100,7 +109,10 @@ class SectionHeaderNodeControllerImpl @Inject constructor(
override val view: View
get() = _view!!
+
override fun offerToKeepInParentForAnimation(): Boolean = false
+
override fun removeFromParentIfKeptForAnimation(): Boolean = false
+
override fun resetKeepInParentForAnimation() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index cff5bef9fe69..6b93ee1c435e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -79,11 +79,10 @@ constructor(
/** The notifications that are promoted and ongoing. Sorted by priority order. */
val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> =
if (StatusBarNotifChips.isEnabled) {
- // TODO(b/364653005): Filter all the notifications down to just the promoted ones.
// TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
// instead of being separate.
topLevelRepresentativeNotifications
- .map { notifs -> notifs.filter { it.isPromoted } }
+ .map { notifs -> notifs.filter { it.promotedContent != null } }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 81335658679b..64e78e4fbe48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -98,22 +98,17 @@ constructor(
}
/** Are there any pinned heads up rows to display? */
- val hasPinnedRows: Flow<Boolean> by lazy {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
- if (rows.isNotEmpty()) {
- combine(rows.map { it.pinnedStatus }) { pinnedStatus ->
- pinnedStatus.any { it.isPinned }
- }
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(false)
+ val hasPinnedRows: Flow<Boolean> =
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
+ if (rows.isNotEmpty()) {
+ combine(rows.map { it.pinnedStatus }) { pinnedStatus ->
+ pinnedStatus.any { it.isPinned }
}
+ } else {
+ // if the set is empty, there are no flows to combine
+ flowOf(false)
}
}
- }
val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
@@ -143,15 +138,10 @@ constructor(
}
}
- val showHeadsUpStatusBar: Flow<Boolean> by lazy {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
- hasPinnedRows && canShowHeadsUp
- }
+ val showHeadsUpStatusBar =
+ combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+ hasPinnedRows && canShowHeadsUp
}
- }
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 8bd7a1ab7a77..042389f7fde7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,7 +33,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -52,7 +51,6 @@ class RenderNotificationListInteractor
constructor(
private val repository: ActiveNotificationListRepository,
private val sectionStyleProvider: SectionStyleProvider,
- private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
/**
* Sets the current list of rendered notification entries as displayed in the notification list.
@@ -60,11 +58,7 @@ constructor(
fun setRenderedList(entries: List<ListEntry>) {
traceSection("RenderNotificationListInteractor.setRenderedList") {
repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(
- existingModels,
- sectionStyleProvider,
- promotedNotificationsProvider,
- ) {
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
entries.forEach(::addListEntry)
setRankingsMap(entries)
}
@@ -76,21 +70,13 @@ constructor(
private fun buildActiveNotificationsStore(
existingModels: ActiveNotificationsStore,
sectionStyleProvider: SectionStyleProvider,
- promotedNotificationsProvider: PromotedNotificationsProvider,
block: ActiveNotificationsStoreBuilder.() -> Unit,
): ActiveNotificationsStore =
- ActiveNotificationsStoreBuilder(
- existingModels,
- sectionStyleProvider,
- promotedNotificationsProvider,
- )
- .apply(block)
- .build()
+ ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
private class ActiveNotificationsStoreBuilder(
private val existingModels: ActiveNotificationsStore,
private val sectionStyleProvider: SectionStyleProvider,
- private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
private val builder = ActiveNotificationsStore.Builder()
@@ -163,7 +149,6 @@ private class ActiveNotificationsStoreBuilder(
key = key,
groupKey = sbn.groupKey,
whenTime = sbn.notification.`when`,
- isPromoted = promotedNotificationsProvider.shouldPromote(this),
isAmbient = sectionStyleProvider.isMinimized(this),
isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
@@ -190,7 +175,6 @@ private fun ActiveNotificationsStore.createOrReuse(
key: String,
groupKey: String?,
whenTime: Long,
- isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -215,7 +199,6 @@ private fun ActiveNotificationsStore.createOrReuse(
key = key,
groupKey = groupKey,
whenTime = whenTime,
- isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -240,7 +223,6 @@ private fun ActiveNotificationsStore.createOrReuse(
key = key,
groupKey = groupKey,
whenTime = whenTime,
- isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -266,7 +248,6 @@ private fun ActiveNotificationModel.isCurrent(
key: String,
groupKey: String?,
whenTime: Long,
- isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -290,7 +271,6 @@ private fun ActiveNotificationModel.isCurrent(
key != this.key -> false
groupKey != this.groupKey -> false
whenTime != this.whenTime -> false
- isPromoted != this.isPromoted -> false
isAmbient != this.isAmbient -> false
isRowDismissed != this.isRowDismissed -> false
isSilent != this.isSilent -> 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 99df9f45840a..0b188afa1c35 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
@@ -42,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+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.notification.collection.NotificationEntry;
@@ -118,8 +119,7 @@ public class HeadsUpManagerImpl
protected int mAutoDismissTime;
protected DelayableExecutor mExecutor;
- @VisibleForTesting
- public final int mExtensionTime;
+ private final int mExtensionTime;
// TODO(b/328393698) move the topHeadsUpRow logic to an interactor
private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -189,7 +189,7 @@ public class HeadsUpManagerImpl
KeyguardBypassController bypassController,
GroupMembershipManager groupMembershipManager,
VisualStabilityProvider visualStabilityProvider,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
@Main Handler handler,
GlobalSettings globalSettings,
SystemClock systemClock,
@@ -212,7 +212,8 @@ public class HeadsUpManagerImpl
mVisualStabilityProvider = visualStabilityProvider;
Resources resources = context.getResources();
mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
- ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ ? resources.getInteger(R.integer.heads_up_notification_minimum_time_with_throttling)
+ : resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
R.integer.sticky_heads_up_notification_time);
mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 3c8c42f6b29d..0f19d7288f6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -25,7 +25,11 @@ import javax.inject.Inject
/** Testable wrapper around Context. */
class IconBuilder @Inject constructor(private val context: Context) {
- fun createIconView(entry: NotificationEntry): StatusBarIconView {
+ @JvmOverloads
+ fun createIconView(
+ entry: NotificationEntry,
+ context: Context = this.context,
+ ): StatusBarIconView {
return StatusBarIconView(
context,
"${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
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 47171948f395..b56a838a80a5 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
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.icon
import android.app.Notification
import android.app.Notification.MessagingStyle
import android.app.Person
+import android.content.Context
import android.content.pm.LauncherApps
import android.graphics.drawable.Icon
import android.os.Build
@@ -36,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -68,6 +70,17 @@ constructor(
@Background private val bgCoroutineContext: CoroutineContext,
@Main private val mainCoroutineContext: CoroutineContext,
) : ConversationIconManager {
+
+ /**
+ * A listener that is notified when a [NotificationEntry] has been updated and the associated
+ * icons have to be updated as well.
+ */
+ fun interface OnIconUpdateRequiredListener {
+ fun onIconUpdateRequired(entry: NotificationEntry)
+ }
+
+ private val onIconUpdateRequiredListeners = mutableSetOf<OnIconUpdateRequiredListener>()
+
private var unimportantConversationKeys: Set<String> = emptySet()
/**
* A map of running jobs for fetching the person avatar from launcher. The key is the
@@ -76,6 +89,16 @@ constructor(
private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
ConcurrentHashMap<String, Job>()
+ fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
+ StatusBarConnectedDisplays.assertInNewMode()
+ onIconUpdateRequiredListeners += listener
+ }
+
+ fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
+ StatusBarConnectedDisplays.assertInNewMode()
+ onIconUpdateRequiredListeners -= listener
+ }
+
fun attach() {
notifCollection.addCollectionListener(entryListener)
}
@@ -112,6 +135,21 @@ constructor(
}
/**
+ * Inflate the [StatusBarIconView] for the given [NotificationEntry], using the specified
+ * [Context].
+ */
+ fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView =
+ traceSection("IconManager.createSbIconView") {
+ StatusBarConnectedDisplays.assertInNewMode()
+
+ val sbIcon = iconBuilder.createIconView(entry, context)
+ sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ val (normalIconDescriptor, _) = getIconDescriptors(entry)
+ setIcon(entry, normalIconDescriptor, sbIcon)
+ return sbIcon
+ }
+
+ /**
* Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
* result in [NotificationEntry.getIcons].
*
@@ -124,7 +162,9 @@ constructor(
val sbIcon = iconBuilder.createIconView(entry)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
val sbChipIcon: StatusBarIconView?
- if (Flags.statusBarCallChipNotificationIcon()) {
+ if (
+ Flags.statusBarCallChipNotificationIcon() && !StatusBarConnectedDisplays.isEnabled
+ ) {
sbChipIcon = iconBuilder.createIconView(entry)
sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
} else {
@@ -159,6 +199,18 @@ constructor(
}
}
+ /** Update the [StatusBarIconView] for the given [NotificationEntry]. */
+ fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) =
+ traceSection("IconManager.updateSbIcon") {
+ StatusBarConnectedDisplays.assertInNewMode()
+
+ val (normalIconDescriptor, _) = getIconDescriptors(entry)
+ val notificationContentDescription =
+ entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) }
+ iconView.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, normalIconDescriptor, iconView)
+ }
+
/**
* Update the notification icons.
*
@@ -172,6 +224,10 @@ constructor(
return@traceSection
}
+ if (StatusBarConnectedDisplays.isEnabled) {
+ onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) }
+ }
+
if (usingCache && !Flags.notificationsBackgroundIcons()) {
Log.wtf(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt
new file mode 100644
index 000000000000..227a1fefb982
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.notification.icon.ui.viewbinder
+
+import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractor
+import com.android.systemui.lifecycle.Activatable
+import com.android.systemui.statusbar.StatusBarIconView
+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.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.icon.IconManager
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+
+/** [IconViewStore] for the status bar on multiple displays. */
+class ConnectedDisplaysStatusBarNotificationIconViewStore
+@AssistedInject
+constructor(
+ @Assisted private val displayId: Int,
+ private val notifCollection: NotifCollection,
+ private val iconManager: IconManager,
+ private val displayWindowPropertiesInteractor: DisplayWindowPropertiesInteractor,
+ private val notifPipeline: NotifPipeline,
+) : IconViewStore, Activatable {
+
+ private val cachedIcons = ConcurrentHashMap<String, StatusBarIconView>()
+
+ private val iconUpdateRequiredListener =
+ object : IconManager.OnIconUpdateRequiredListener {
+ override fun onIconUpdateRequired(entry: NotificationEntry) {
+ val iconView = iconView(entry.key) ?: return
+ iconManager.updateSbIcon(entry, iconView)
+ }
+ }
+
+ private val notifCollectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ cachedIcons.remove(entry.key)
+ }
+ }
+
+ override fun iconView(key: String): StatusBarIconView? {
+ val entry = notifCollection.getEntry(key) ?: return null
+ return cachedIcons.computeIfAbsent(key) {
+ val context = displayWindowPropertiesInteractor.getForStatusBar(displayId).context
+ iconManager.createSbIconView(context, entry)
+ }
+ }
+
+ override suspend fun activate() = coroutineScope {
+ start()
+ try {
+ awaitCancellation()
+ } finally {
+ stop()
+ }
+ }
+
+ private fun start() {
+ notifPipeline.addCollectionListener(notifCollectionListener)
+ iconManager.addIconsUpdateListener(iconUpdateRequiredListener)
+ }
+
+ private fun stop() {
+ notifPipeline.removeCollectionListener(notifCollectionListener)
+ iconManager.removeIconsUpdateListener(iconUpdateRequiredListener)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int): ConnectedDisplaysStatusBarNotificationIconViewStore
+ }
+}
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 a21dabb821d4..aa81ebf22ac6 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
@@ -16,6 +16,7 @@
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
@@ -29,6 +30,7 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.ui.SystemBarUtilsState
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
/** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
class NotificationIconContainerStatusBarViewBinder
@@ -38,11 +40,22 @@ constructor(
@ShadeDisplayAware private val configuration: ConfigurationState,
private val systemBarUtilsState: SystemBarUtilsState,
private val failureTracker: StatusBarIconViewBindingFailureTracker,
- private val viewStore: StatusBarNotificationIconViewStore,
+ private val defaultDisplayViewStore: StatusBarNotificationIconViewStore,
+ private val connectedDisplaysViewStoreFactory:
+ ConnectedDisplaysStatusBarNotificationIconViewStore.Factory,
) {
+
fun bindWhileAttached(view: NotificationIconContainer, displayId: Int): DisposableHandle {
return traceSection("NICStatusBar#bindWhileAttached") {
view.repeatWhenAttached {
+ val viewStore =
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ defaultDisplayViewStore
+ } else {
+ connectedDisplaysViewStoreFactory.create(displayId = displayId).also {
+ lifecycleScope.launch { it.activate() }
+ }
+ }
lifecycleScope.launch {
NotificationIconContainerViewBinder.bind(
displayId = displayId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 8768ea267b3f..f86ae684777f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel
import android.content.res.Resources
import com.android.systemui.dagger.SysUISingleton
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.KeyguardState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
import javax.inject.Inject
@@ -44,17 +44,16 @@ constructor(
iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
- @Main resources: Resources,
+ @ShadeDisplayAware resources: Resources,
shadeInteractor: ShadeInteractor,
) {
private val maxIcons = resources.getInteger(R.integer.max_notif_icons_on_aod)
/** Are changes to the icon container animated? */
val areContainerChangesAnimated: Flow<Boolean> =
- combine(
- shadeInteractor.isShadeTouchable,
- keyguardInteractor.isKeyguardVisible,
- ) { panelTouchesEnabled, isKeyguardVisible ->
+ combine(shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardVisible) {
+ panelTouchesEnabled,
+ isKeyguardVisible ->
panelTouchesEnabled && isKeyguardVisible
}
.flowOn(bgContext)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index f400d605cb43..38eaf27ad358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -27,6 +27,7 @@ import android.app.Notification.EXTRA_TITLE
import android.app.Notification.ProgressStyle
import android.content.Context
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
@@ -38,7 +39,7 @@ class PromotedNotificationContentExtractor
@Inject
constructor(
private val promotedNotificationsProvider: PromotedNotificationsProvider,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val logger: PromotedNotificationLogger,
) {
fun extractContent(
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 d538f52fd637..5c51adadfd82 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
@@ -46,7 +46,6 @@ import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.util.DumpUtilsKt;
@@ -407,12 +406,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- if (NotificationsImprovedHunAnimation.isEnabled()
- || NotificationHeadsUpCycling.isEnabled()) {
- mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
- } else {
- mAppearAnimator.setInterpolator(Interpolators.LINEAR);
- }
+ mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
mAppearAnimator.addUpdateListener(animation -> {
@@ -531,10 +525,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
* @param clipSide Which side if view we want to clip from
*/
private void updateAppearRect(ClipSide clipSide) {
- float interpolatedFraction =
- NotificationsImprovedHunAnimation.isEnabled()
- || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
- : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
+ float interpolatedFraction = mAppearAnimationFraction;
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
final int fullHeight = getActualHeight();
float height = fullHeight * interpolatedFraction;
@@ -566,14 +557,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
updateAppearRect(ClipSide.BOTTOM);
}
- private float getInterpolatedAppearAnimationFraction() {
-
- if (mAppearAnimationFraction >= 0) {
- return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
- }
- return 1.0f;
- }
-
private void updateAppearAnimationAlpha() {
updateAppearAnimationContentAlpha(
mAppearAnimationFraction,
@@ -643,26 +626,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
super.applyRoundnessAndInvalidate();
}
- @Override
- public float getTopCornerRadius() {
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- return super.getTopCornerRadius();
- }
-
- float fraction = getInterpolatedAppearAnimationFraction();
- return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
- }
-
- @Override
- public float getBottomCornerRadius() {
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- return super.getBottomCornerRadius();
- }
-
- float fraction = getInterpolatedAppearAnimationFraction();
- return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
- }
-
private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
mBackgroundNormal.setRadius(topRadius, bottomRadius);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt
deleted file mode 100644
index aa63f4ddbd45..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt
+++ /dev/null
@@ -1,61 +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.statusbar.notification.row
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the ensure enr views visibility flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object EnsureEnrViewsVisibility {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_ENSURE_ENR_VIEWS_VISIBILITY
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.ensureEnrViewsVisibility()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is not enabled to ensure that the refactor author catches issues in testing.
- * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
- */
- @JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
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 b7ab996a608c..c8811fd3b76d 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
@@ -16,6 +16,7 @@
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;
@@ -78,7 +79,6 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.systemui.Flags;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
@@ -102,6 +102,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
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;
+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;
@@ -121,7 +122,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.SwipeableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -129,14 +129,12 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.ListenerSet;
-import com.android.systemui.wmshell.BubblesManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -150,7 +148,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationFadeAware.FadeOptimizedNotification {
private static final String TAG = "ExpandableNotifRow";
- private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean DEBUG_ONMEASURE =
Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final int MENU_VIEW_INDEX = 0;
@@ -185,12 +182,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private LayoutListener mLayoutListener;
private RowContentBindStage mRowContentBindStage;
private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- private Optional<BubblesManager> mBubblesManagerOptional;
private MetricsLogger mMetricsLogger;
private NotificationChildrenContainerLogger mChildrenContainerLogger;
private ColorUpdateLogger mColorUpdateLogger;
private NotificationDismissibilityProvider mDismissibilityProvider;
- private FeatureFlags mFeatureFlags;
private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
@@ -341,7 +336,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mIgnoreLockscreenConstraints;
- private OnClickListener mExpandClickListener = new OnClickListener() {
+ private final OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
toggleExpansionState(v, /* shouldLogExpandClickMetric = */true);
@@ -430,13 +425,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Nullable
private Runnable mOnIntrinsicHeightReachedRunnable;
- private float mTopRoundnessDuringLaunchAnimation;
- private float mBottomRoundnessDuringLaunchAnimation;
- private float mSmallRoundness;
+ private final float mSmallRoundness;
- private ListenerSet<DismissButtonTargetVisibilityListener>
- mDismissButtonTargetVisibilityListeners
- = new ListenerSet();
+ private final ListenerSet<DismissButtonTargetVisibilityListener>
+ mDismissButtonTargetVisibilityListeners = new ListenerSet<>();
public NotificationContentView[] getLayouts() {
return Arrays.copyOf(mLayouts, mLayouts.length);
@@ -787,7 +779,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (targetVisible != null) {
for (DismissButtonTargetVisibilityListener listener :
mDismissButtonTargetVisibilityListeners) {
- listener.onTargetVisibilityChanged(targetVisible.booleanValue());
+ listener.onTargetVisibilityChanged(targetVisible);
}
}
@@ -2023,7 +2015,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
StatusBarStateController statusBarStateController,
PeopleNotificationIdentifier peopleNotificationIdentifier,
OnUserInteractionCallback onUserInteractionCallback,
- Optional<BubblesManager> bubblesManagerOptional,
NotificationGutsManager gutsManager,
NotificationDismissibilityProvider dismissibilityProvider,
MetricsLogger metricsLogger,
@@ -2031,7 +2022,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
ColorUpdateLogger colorUpdateLogger,
SmartReplyConstants smartReplyConstants,
SmartReplyController smartReplyController,
- FeatureFlags featureFlags,
IStatusBarService statusBarService,
UiEventLogger uiEventLogger) {
mEntry = entry;
@@ -2066,13 +2056,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
);
}
mOnUserInteractionCallback = onUserInteractionCallback;
- mBubblesManagerOptional = bubblesManagerOptional;
mNotificationGutsManager = gutsManager;
mMetricsLogger = metricsLogger;
mChildrenContainerLogger = childrenContainerLogger;
mColorUpdateLogger = colorUpdateLogger;
mDismissibilityProvider = dismissibilityProvider;
- mFeatureFlags = featureFlags;
setHapticFeedbackEnabled(!Flags.msdlFeedback());
}
@@ -2083,8 +2071,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height_before_p);
mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_before_s);
- mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_min_height);
+ if (notificationsRedesignTemplates()) {
+ mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_2025_min_height);
+ } else {
+ mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height);
+ }
mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
@@ -2464,10 +2457,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
- public CharSequence getActiveRemoteInputText() {
- return mPrivateLayout.getActiveRemoteInputText();
- }
-
/**
* Reset the translation with an animation.
*/
@@ -2542,7 +2531,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return getTranslationX();
}
- if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
+ if (mTranslateableViews != null && !mTranslateableViews.isEmpty()) {
// All of the views in the list should have same translation, just use first one.
return mTranslateableViews.get(0).getTranslationX();
}
@@ -2597,10 +2586,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private void updateChildrenVisibility() {
- if (EnsureEnrViewsVisibility.isEnabled()) {
- mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
- }
-
boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
&& mGuts.isExposed();
mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren
@@ -2681,7 +2666,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress());
if (mNotificationParent != null) {
float parentTranslationY = mNotificationParent.getTranslationY();
- top -= parentTranslationY;
+ top -= (int) parentTranslationY;
mNotificationParent.setTranslationZ(translationZ);
// When the expanding notification is below its parent, the parent must be clipped
@@ -2708,9 +2693,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
float absoluteCenterX = getLocationOnScreen()[0] + getWidth() / 2f - getTranslationX();
setTranslationX(params.getCenterX() - absoluteCenterX);
- final float maxRadius = getMaxRadius();
- mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
- mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
invalidateOutline();
mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
@@ -2920,7 +2902,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
-
@Override
public int getHeightWithoutLockscreenConstraints() {
mIgnoreLockscreenConstraints = true;
@@ -3160,13 +3141,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
mLogger.logSkipResetAllContentAlphas(getEntry());
}
-
- if (!EnsureEnrViewsVisibility.isEnabled()) {
- // mPublicLayout.setVisibility moved to updateChildrenVisibility when the flag is on
- // in order to ensure public and private views are not visible
- // together at the same time.
- mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
- }
+ mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
updateChildrenVisibility();
} else {
animateShowingPublic(delay, duration, mShowingPublic);
@@ -3252,7 +3227,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
notifyHeightChanged(/* needsAnimation= */ false);
}
- public void setChildrenExpanded(boolean expanded, boolean animate) {
+ public void setChildrenExpanded(boolean expanded) {
mChildrenExpanded = expanded;
if (mChildrenContainer != null) {
mChildrenContainer.setChildrenExpanded(expanded);
@@ -3904,10 +3879,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mEntry.getSbn().getNotification().isMediaNotification();
}
- public boolean isGroupNotFullyVisible() {
- return getClipTopAmount() > 0 || getTranslationY() < 0;
- }
-
public void setAboveShelf(boolean aboveShelf) {
boolean wasAboveShelf = isAboveShelf();
mAboveShelf = aboveShelf;
@@ -4136,10 +4107,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTargetPoint = p;
}
- public Point getTargetPoint() {
- return mTargetPoint;
- }
-
/** Update the minimum roundness based on current state */
private void updateBaseRoundness() {
if (isChildInGroup()) {
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 a150f7f1f54e..e06280e36bc8 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
@@ -35,7 +35,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginManager;
@@ -51,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
@@ -59,17 +60,14 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.time.SystemClock;
-import com.android.systemui.wmshell.BubblesManager;
import com.google.android.msdl.data.model.MSDLToken;
import com.google.android.msdl.domain.MSDLPlayer;
import java.util.List;
-import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@@ -108,10 +106,9 @@ public class ExpandableNotificationRowController implements NotifViewController
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
private final FalsingManager mFalsingManager;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final boolean mAllowLongPress;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- private final Optional<BubblesManager> mBubblesManagerOptional;
private final SmartReplyConstants mSmartReplyConstants;
private final SmartReplyController mSmartReplyController;
private final ExpandableNotificationRowDragController mDragController;
@@ -271,9 +268,8 @@ public class ExpandableNotificationRowController implements NotifViewController
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
OnUserInteractionCallback onUserInteractionCallback,
FalsingManager falsingManager,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
PeopleNotificationIdentifier peopleNotificationIdentifier,
- Optional<BubblesManager> bubblesManagerOptional,
NotificationSettingsController settingsController,
ExpandableNotificationRowDragController dragController,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -303,7 +299,6 @@ public class ExpandableNotificationRowController implements NotifViewController
mAllowLongPress = allowLongPress;
mFeatureFlags = featureFlags;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
- mBubblesManagerOptional = bubblesManagerOptional;
mSettingsController = settingsController;
mDragController = dragController;
mMetricsLogger = metricsLogger;
@@ -340,7 +335,6 @@ public class ExpandableNotificationRowController implements NotifViewController
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
- mBubblesManagerOptional,
mNotificationGutsManager,
mDismissibilityProvider,
mMetricsLogger,
@@ -348,7 +342,6 @@ public class ExpandableNotificationRowController implements NotifViewController
mColorUpdateLogger,
mSmartReplyConstants,
mSmartReplyController,
- mFeatureFlags,
mStatusBarService,
mUiEventLogger
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 11db2fc77170..d5551b16842e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -50,6 +50,7 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
@@ -69,7 +70,8 @@ public class ExpandableNotificationRowDragController {
private NotificationPanelLogger mNotificationPanelLogger;
@Inject
- public ExpandableNotificationRowDragController(Context context,
+ public ExpandableNotificationRowDragController(
+ @ShadeDisplayAware Context context,
HeadsUpManager headsUpManager,
ShadeController shadeController,
NotificationPanelLogger notificationPanelLogger) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index a323c2604c63..80cf818e985f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -30,7 +30,6 @@ import android.view.ViewOutlineProvider;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.RoundableState;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.util.DumpUtilsKt;
@@ -123,15 +122,6 @@ public abstract class ExpandableOutlineView extends ExpandableView {
return EMPTY_PATH;
}
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
- float overShoot = topRadius + bottomRadius - height;
- float currentTopRoundness = getTopRoundness();
- float currentBottomRoundness = getBottomRoundness();
- topRadius -= overShoot * currentTopRoundness
- / (currentTopRoundness + currentBottomRoundness);
- bottomRadius -= overShoot * currentBottomRoundness
- / (currentTopRoundness + currentBottomRoundness);
- }
getRoundedRectPath(left, top, right, bottom, topRadius, bottomRadius, mTmpPath);
return mTmpPath;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index ea508748fada..9712db8a1812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -59,6 +59,7 @@ import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
@@ -134,7 +135,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
private final ActivityStarter mActivityStarter;
@Inject
- public NotificationGutsManager(Context context,
+ public NotificationGutsManager(
+ @ShadeDisplayAware Context context,
@Main Handler mainHandler,
@Background Handler bgHandler,
JavaAdapter javaAdapter,
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 a2b71551eca8..ab8be306ab5e 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
@@ -38,9 +38,6 @@ data class ActiveNotificationModel(
val groupKey: String?,
/** When this notification was posted. */
val whenTime: Long,
- // TODO(b/377566661): Make isPromoted just check if promotedContent != null.
- /** True if this notification should be promoted and false otherwise. */
- val isPromoted: Boolean,
/** Is this entry in the ambient / minimized section (lowest priority)? */
val isAmbient: Boolean,
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ad3611796d62..64ca81545040 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -30,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -297,7 +298,7 @@ public class AmbientState implements Dumpable {
@Inject
public AmbientState(
- @NonNull Context context,
+ @NonNull @ShadeDisplayAware Context context,
@NonNull DumpManager dumpManager,
@NonNull SectionProvider sectionProvider,
@NonNull BypassController bypassController,
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 cfca8307e703..99edf652f289 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
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.app.Flags.notificationsRedesignTemplates;
+
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
@@ -171,7 +173,9 @@ public class NotificationChildrenContainer extends ViewGroup
R.dimen.notification_children_container_margin_top);
mNotificationTopPadding = res.getDimensionPixelOffset(
R.dimen.notification_children_container_top_padding);
- mHeaderHeight = mNotificationHeaderMargin + mNotificationTopPadding;
+ mHeaderHeight = notificationsRedesignTemplates()
+ ? res.getDimensionPixelSize(R.dimen.notification_2025_header_height)
+ : mNotificationHeaderMargin + mNotificationTopPadding;
mCollapsedBottomPadding = res.getDimensionPixelOffset(
R.dimen.notification_children_collapsed_bottom_padding);
mEnableShadowOnChildNotifications =
@@ -499,7 +503,7 @@ public class NotificationChildrenContainer extends ViewGroup
mGroupHeaderWrapper.setExpanded(mChildrenExpanded);
mGroupHeaderWrapper.onContentUpdated(mContainingNotification);
-
+ resetHeaderVisibilityIfNeeded(mGroupHeader, calculateDesiredHeader());
updateHeaderVisibility(false /* animate */);
updateChildrenAppearance();
@@ -535,6 +539,7 @@ public class NotificationChildrenContainer extends ViewGroup
invalidate();
mMinimizedGroupHeaderWrapper.onContentUpdated(mContainingNotification);
+ resetHeaderVisibilityIfNeeded(mMinimizedGroupHeader, calculateDesiredHeader());
updateHeaderVisibility(false /* animate */);
updateChildrenAppearance();
}
@@ -1127,7 +1132,7 @@ public class NotificationChildrenContainer extends ViewGroup
final int count = mAttachedChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableNotificationRow child = mAttachedChildren.get(childIdx);
- child.setChildrenExpanded(childrenExpanded, false);
+ child.setChildrenExpanded(childrenExpanded);
}
updateHeaderTouchability();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 31e4d2cac50c..043d64e46039 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -21,7 +21,6 @@ import android.view.View
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
@@ -36,6 +35,7 @@ import com.android.systemui.statusbar.notification.dagger.SilentHeader
import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.foldToSparseArray
@@ -51,7 +51,6 @@ class NotificationSectionsManager
internal constructor(
@ShadeDisplayAware private val configurationController: ConfigurationController,
private val keyguardMediaController: KeyguardMediaController,
- private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val mediaContainerController: MediaContainerController,
private val notificationRoundnessManager: NotificationRoundnessManager,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@@ -120,8 +119,8 @@ internal constructor(
}
fun createSectionsForBuckets(): Array<NotificationSection> =
- sectionsFeatureManager
- .getNotificationBuckets()
+ PriorityBucket
+ .getAllInOrder()
.map { NotificationSection(it) }
.toTypedArray()
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 223475e6e3b8..b9e38abf8ab2 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
@@ -99,7 +99,6 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -110,6 +109,8 @@ import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyS
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -118,7 +119,6 @@ import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent;
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -127,7 +127,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrol
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.Assert;
@@ -3459,11 +3458,8 @@ public class NotificationStackScrollLayout
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- // TODO(b/283084712) remove this with the flag and update the HUN filters at
- // creation
- event.filter.animateHeight = false;
- }
+ // TODO(b/283084712) remove this and update the HUN filters at creation
+ event.filter.animateHeight = false;
mAnimationEvents.add(event);
if (SPEW) {
Log.v(TAG, "Generating HUN animation event: "
@@ -6864,7 +6860,7 @@ public class NotificationStackScrollLayout
mExpandedGroupView = changedRow;
mNeedsAnimation = true;
}
- changedRow.setChildrenExpanded(expanded, animated);
+ changedRow.setChildrenExpanded(expanded);
onChildHeightChanged(changedRow, false /* needsAnimation */);
runAfterAnimationFinished(new Runnable() {
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 e89645d7cb94..ba707a5cd0b1 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
@@ -86,6 +86,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -734,7 +735,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
TunerService tunerService,
DeviceProvisionedController deviceProvisionedController,
DynamicPrivacyController dynamicPrivacyController,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
KeyguardMediaController keyguardMediaController,
KeyguardBypassController keyguardBypassController,
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 5dff8120f33f..a96d972af2c4 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
@@ -22,9 +22,9 @@ import android.view.View.GONE
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -60,7 +60,7 @@ constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
private val mediaDataManager: MediaDataManager,
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val splitShadeStateController: SplitShadeStateController,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
@Application private val scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 0e94ca351835..50457449f466 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -40,6 +40,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -560,7 +561,7 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
private NotificationRoundnessManager mNotificationRoundnessManager;
@Inject
- Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
+ Builder(@ShadeDisplayAware Resources resources, ViewConfiguration viewConfiguration,
DumpManager dumpManager,
FalsingManager falsingManager, FeatureFlags featureFlags,
NotificationRoundnessManager notificationRoundnessManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 969ff1b4ffe7..44075afa6b40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK;
+
import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.annotation.StringRes;
@@ -28,6 +30,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.core.view.ViewCompat;
+
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -69,6 +73,13 @@ public class SectionHeaderView extends StackScrollerDecorView {
mLabelView.setText(mLabelTextId);
}
mLabelView.setAccessibilityHeading(true);
+ ViewCompat.replaceAccessibilityAction(
+ mLabelView,
+ ACTION_CLICK,
+ getResources().getString(
+ R.string.accessibility_notification_section_header_open_settings),
+ null
+ );
}
@Override
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 b251b078826b..1653029dc994 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
@@ -41,7 +41,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.List;
@@ -1065,8 +1064,7 @@ public class StackScrollAlgorithm {
headsUpTranslation);
childState.setYTranslation(inSpaceTranslation + extraTranslation);
cyclingInHunHeight = -1;
- } else
- if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
+ } else if (!ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
childState.setYTranslation(
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 058233ffb93e..4686bef9ca5a 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
@@ -37,8 +37,6 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.HashSet;
@@ -179,10 +177,7 @@ public class StackStateAnimator {
mHeadsUpDisappearChildren.clear();
mNewEvents.clear();
mNewAddChildren.clear();
- if (NotificationsImprovedHunAnimation.isEnabled()
- || NotificationHeadsUpCycling.isEnabled()) {
- mAnimationProperties.resetCustomInterpolators();
- }
+ mAnimationProperties.resetCustomInterpolators();
}
private void initAnimationProperties(ExpandableView child,
@@ -498,8 +493,7 @@ public class StackStateAnimator {
}
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
/* isHeadsUpAppear= */ true, onAnimationEnd);
- } else if (NotificationsImprovedHunAnimation.isEnabled()
- && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
@@ -602,30 +596,6 @@ public class StackStateAnimator {
endRunnable.run();
}
needsCustomAnimation |= needsAnimation;
- } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
- NotificationsImprovedHunAnimation.assertInLegacyMode();
- // This item is added, initialize its properties.
- ExpandableViewState viewState = changingView.getViewState();
- mTmpState.copyFrom(viewState);
- if (event.headsUpFromBottom) {
- mTmpState.setYTranslation(mHeadsUpAppearHeightBottom);
- } else {
- Runnable onAnimationEnd = null;
- if (loggable) {
- String finalKey = key;
- onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
- }
- changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
- true /* isHeadsUpAppear */, onAnimationEnd);
- }
- mHeadsUpAppearChildren.add(changingView);
- // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
- // ADD animations, which would not be logged here.
- if (loggable) {
- mLogger.logHUNViewAppearing(key);
- }
-
- mTmpState.applyToView(changingView);
} else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
|| event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
mHeadsUpDisappearChildren.add(changingView);
@@ -636,14 +606,10 @@ public class StackStateAnimator {
// transiently
mHostLayout.addTransientView(changingView, 0);
changingView.setTransientContainer(mHostLayout);
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- // 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)
- );
- }
+ // 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));
endRunnable = changingView::removeFromTransientContainer;
}
@@ -697,14 +663,12 @@ public class StackStateAnimator {
startAnimation, postAnimation,
getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
mAnimationProperties.delay += removeAnimationDelay;
- if (NotificationsImprovedHunAnimation.isEnabled()) {
- mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
- mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
- Interpolators.FAST_OUT_SLOW_IN_REVERSE);
- mAnimationProperties.getAnimationFilter().animateY = true;
- mTmpState.animateTo(changingView, mAnimationProperties);
- mAnimationProperties.resetCustomInterpolators();
- }
+ mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+ mAnimationProperties.getAnimationFilter().animateY = true;
+ mTmpState.animateTo(changingView, mAnimationProperties);
+ mAnimationProperties.resetCustomInterpolators();
} else if (endRunnable != null) {
endRunnable.run();
}
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 a55a165d9b66..f8f29ff59154 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
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
@@ -116,7 +117,7 @@ constructor(
private val interactor: SharedNotificationContainerInteractor,
dumpManager: DumpManager,
@Application applicationScope: CoroutineScope,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -124,6 +125,8 @@ constructor(
private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
+ private val alternateBouncerToPrimaryBouncerTransitionViewModel:
+ AlternateBouncerToPrimaryBouncerTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
@@ -560,6 +563,7 @@ constructor(
lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+ alternateBouncerToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToGoneTransitionViewModel.notificationAlpha(viewState),
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index d1338eadb6b5..f2ef2f0ab48f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -313,6 +313,7 @@ constructor(
// if it is volume panel.
options.setDisallowEnterPictureInPictureWhileLaunching(true)
}
+ intent.collectExtraIntentKeys()
try {
result[0] =
ActivityTaskManager.getService()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 7f95fb072ede..adfcb710da92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -53,6 +53,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
@@ -209,10 +210,16 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
@Override
public void clickTile(ComponentName tile) {
- // Can't inject this because it changes with the QS fragment
- QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
- if (qsPanelController != null) {
- qsPanelController.clickTile(tile);
+ if (QsInCompose.isEnabled()) {
+ if (tile != null) {
+ mQSHost.clickTile(tile);
+ }
+ } else {
+ // Can't inject this because it changes with the QS fragment
+ QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
+ if (qsPanelController != null) {
+ qsPanelController.clickTile(tile);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index b9639a7a90f2..6cad68ffe8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -27,7 +27,6 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.dagger.qualifiers.DisplaySpecific;
-import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -37,19 +36,21 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
@@ -97,7 +98,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
@VisibleForTesting
float mAppearFraction;
private ExpandableNotificationRow mTrackedChild;
- private boolean mShown;
+ private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned;
private final ViewClippingUtil.ClippingParameters mParentClippingParams =
new ViewClippingUtil.ClippingParameters() {
@Override
@@ -107,7 +108,6 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
};
private boolean mAnimationsEnabled = true;
private final KeyguardStateController mKeyguardStateController;
- private final FeatureFlagsClassic mFeatureFlags;
private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
@VisibleForTesting
@@ -126,7 +126,6 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
NotificationRoundnessManager notificationRoundnessManager,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
- FeatureFlagsClassic featureFlags,
HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
@@ -144,7 +143,6 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
mStackScrollerController = stackScrollerController;
mShadeViewController = shadeViewController;
- mFeatureFlags = featureFlags;
mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
@@ -156,7 +154,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (shouldBeVisible()) {
- updateTopEntry("onLayoutChange");
+ updateTopEntry();
// trigger scroller to notify the latest panel translation
mStackScrollerController.requestLayout();
@@ -206,7 +204,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- updateTopEntry("onHeadsUpPinned");
+ updateTopEntry();
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -217,7 +215,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
- private void updateTopEntry(String reason) {
+ private void updateTopEntry() {
NotificationEntry newEntry = null;
if (shouldBeVisible()) {
newEntry = mHeadsUpManager.getTopEntry();
@@ -225,34 +223,40 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
NotificationEntry previousEntry = mView.getShowingEntry();
mView.setEntry(newEntry);
if (newEntry != previousEntry) {
- boolean animateIsolation = false;
if (newEntry == null) {
// no heads up anymore, lets start the disappear animation
-
- setShown(false);
- animateIsolation = !isExpanded();
+ setPinnedStatus(PinnedStatus.NotPinned);
} else if (previousEntry == null) {
// We now have a headsUp and didn't have one before. Let's start the disappear
// animation
- setShown(true);
- animateIsolation = !isExpanded();
+ setPinnedStatus(PinnedStatus.PinnedBySystem);
}
- mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
- newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
+
+ String isolatedIconKey;
+ if (newEntry != null) {
+ isolatedIconKey = newEntry.getRepresentativeEntry().getKey();
+ } else {
+ isolatedIconKey = null;
+ }
+ mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(isolatedIconKey);
}
}
- private void setShown(boolean isShown) {
- if (mShown != isShown) {
- mShown = isShown;
- if (isShown) {
+ private void setPinnedStatus(PinnedStatus pinnedStatus) {
+ if (mPinnedStatus != pinnedStatus) {
+ mPinnedStatus = pinnedStatus;
+ if (pinnedStatus.isPinned()) {
updateParentClipping(false /* shouldClip */);
mView.setVisibility(View.VISIBLE);
show(mView);
- hide(mClockView, View.INVISIBLE);
+ if (!StatusBarRootModernization.isEnabled()) {
+ hide(mClockView, View.INVISIBLE);
+ }
mOperatorNameViewOptional.ifPresent(view -> hide(view, View.INVISIBLE));
} else {
- show(mClockView);
+ if (!StatusBarRootModernization.isEnabled()) {
+ show(mClockView);
+ }
mOperatorNameViewOptional.ifPresent(this::show);
hide(mView, View.GONE, () -> {
updateParentClipping(true /* shouldClip */);
@@ -325,8 +329,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
}
@VisibleForTesting
- public boolean isShown() {
- return mShown;
+ public PinnedStatus getPinnedStatus() {
+ return mPinnedStatus;
}
/**
@@ -350,7 +354,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- updateTopEntry("onHeadsUpUnPinned");
+ updateTopEntry();
updateHeader(entry);
updateHeadsUpAndPulsingRoundness(entry);
}
@@ -368,7 +372,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
updateHeadsUpHeaders();
}
if (isExpanded() != oldIsExpanded) {
- updateTopEntry("setAppearFraction");
+ updateTopEntry();
}
}
@@ -442,11 +446,11 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
}
public void onStateChanged() {
- updateTopEntry("onStateChanged");
+ updateTopEntry();
}
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
- updateTopEntry("onFullyHiddenChanged");
+ updateTopEntry();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 1cca3ae0a2c0..d7cc65d22663 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -180,6 +180,7 @@ constructor(
// if it is volume panel.
options.setDisallowEnterPictureInPictureWhileLaunching(true)
}
+ intent.collectExtraIntentKeys()
try {
result[0] =
ActivityTaskManager.getService()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index 1a4f3ca5b07f..ea67f1cdb60a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -527,12 +527,16 @@ public class LightBarControllerImpl implements
@Override
public LightBarController create(@NonNull Context context) {
// TODO: b/380394368 - Make sure correct per display instances are used.
- return mFactory.create(
+ LightBarControllerImpl lightBarController = mFactory.create(
context.getDisplayId(),
mApplicationScope,
mDarkIconDispatcherStore.getDefaultDisplay(),
mStatusBarModeRepositoryStore.getDefaultDisplay()
);
+ // Calling start() manually to keep the legacy behavior. Before, LightBarControllerImpl
+ // was doing work in the constructor, which moved to start().
+ lightBarController.start();
+ return lightBarController;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 16e023ce17fd..f19d707046f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -39,7 +39,9 @@ import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.StatusBarLongPressGestureDetector
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
@@ -52,6 +54,7 @@ import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
+import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
@@ -79,6 +82,7 @@ private constructor(
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
private val darkIconDispatcher: DarkIconDispatcher,
private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
+ private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>,
) : ViewController<PhoneStatusBarView>(view) {
private lateinit var battery: BatteryMeterView
@@ -226,6 +230,9 @@ private constructor(
!upOrCancel || shadeController.isExpandedVisible,
)
}
+ if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
+ lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId)
+ }
}
private fun addDarkReceivers() {
@@ -344,6 +351,7 @@ private constructor(
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
@DisplaySpecific private val darkIconDispatcher: DarkIconDispatcher,
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+ private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>,
) {
fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
val statusBarMoveFromCenterAnimationController =
@@ -371,6 +379,7 @@ private constructor(
statusOverlayHoverListenerFactory,
darkIconDispatcher,
statusBarContentInsetsProviderStore.defaultDisplay,
+ lazyStatusBarShadeDisplayPolicy,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index aef26dea3c0d..bd1360f6e939 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -818,7 +818,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showPrimaryBouncer(boolean scrimmed) {
- hideAlternateBouncer(false);
+ hideAlternateBouncer(
+ /* updateScrim= */ false,
+ // When the scene framework is on, don't ever clear the pending dismiss action from
+ /* clearDismissAction= */ !SceneContainerFlag.isEnabled());
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
mSceneInteractorLazy.get().changeScene(
@@ -1005,6 +1008,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void hideAlternateBouncer(boolean updateScrim) {
+ hideAlternateBouncer(updateScrim, /* clearDismissAction= */ true);
+ }
+
+ @Override
+ public void hideAlternateBouncer(boolean updateScrim, boolean clearDismissAction) {
+ if (clearDismissAction) {
+ mKeyguardDismissActionInteractor.get().clearDismissAction();
+ }
+
updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() && updateScrim);
}
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 42b9d5b12afc..4f32aaa2654e 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
@@ -35,8 +35,6 @@ import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerSt
import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
-import com.android.systemui.statusbar.phone.AutoHideController
-import com.android.systemui.statusbar.phone.AutoHideControllerImpl
import com.android.systemui.statusbar.phone.AutoHideControllerStore
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
import com.android.systemui.statusbar.phone.MultiDisplayAutoHideControllerStore
@@ -80,9 +78,6 @@ interface StatusBarPhoneModule {
@Binds fun statusBarInitializer(@Default impl: StatusBarInitializerImpl): StatusBarInitializer
- @Binds
- fun autoHideControllerFactory(impl: AutoHideControllerImpl.Factory): AutoHideController.Factory
-
companion object {
/** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
@Provides
@@ -128,6 +123,7 @@ interface StatusBarPhoneModule {
statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
initializerStore: StatusBarInitializerStore,
statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ autoHideControllerStore: AutoHideControllerStore,
statusBarOrchestratorFactory: StatusBarOrchestrator.Factory,
): StatusBarOrchestrator {
return statusBarOrchestratorFactory.create(
@@ -137,6 +133,7 @@ interface StatusBarPhoneModule {
statusBarModeRepositoryStore.defaultDisplay,
initializerStore.defaultDisplay,
statusBarWindowControllerStore.defaultDisplay,
+ autoHideControllerStore.defaultDisplay,
)
}
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 23b4b65bb2ac..724ba8cab352 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
@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarCompone
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization;
import com.android.systemui.statusbar.phone.ui.DarkIconManager;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder;
@@ -376,6 +377,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
mHomeStatusBarViewBinder.bind(
+ view.getContext().getDisplayId(),
mStatusBar,
mHomeStatusBarViewModel,
/* systemEventChipAnimateIn */ null,
@@ -504,12 +506,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
notificationIconArea.requireViewById(R.id.notificationIcons);
mNotificationIconAreaInner = notificationIcons;
int displayId = mHomeStatusBarComponent.getDisplayId();
- if (displayId == Display.DEFAULT_DISPLAY) {
- //TODO(b/369337701): implement notification icons for all displays.
- // Currently if we try to bind for all displays, there is a crash, because the same
- // notification icon view can't have multiple parents.
- mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
- }
+ mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
if (!StatusBarRootModernization.isEnabled()) {
updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
@@ -965,7 +962,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mOngoingCallController.addCallback(mOngoingCallListener);
}
// TODO(b/364653005): Do we also need to set the secondary activity chip?
- mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
+ if (!StatusBarChipsModernization.isEnabled()) {
+ mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
+ }
}
@Override
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 78926c78a368..c57cede754d3 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
@@ -60,7 +60,10 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-/** A controller to handle the ongoing call chip in the collapsed status bar. */
+/** A controller to handle the ongoing call chip in the collapsed status bar.
+ * @deprecated Use [OngoingCallInteractor] instead, which follows recommended architecture patterns
+ */
+@Deprecated("Use OngoingCallInteractor instead")
@SysUISingleton
class OngoingCallController
@Inject
@@ -165,6 +168,9 @@ constructor(
}
override fun start() {
+ if (StatusBarChipsModernization.isEnabled)
+ return
+
dumpManager.registerDumpable(this)
if (Flags.statusBarUseReposForCallChip()) {
@@ -201,6 +207,8 @@ constructor(
* [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment].
*/
fun setChipView(chipView: View) {
+ StatusBarChipsModernization.assertInLegacyMode()
+
tearDownChipView()
this.chipView = chipView
val backgroundView: ChipBackgroundContainer? =
@@ -217,6 +225,8 @@ constructor(
* Returns true if there's an active ongoing call that should be displayed in a status bar chip.
*/
fun hasOngoingCall(): Boolean {
+ StatusBarChipsModernization.assertInLegacyMode()
+
return callNotificationInfo?.isOngoing == true &&
// When the user is in the phone app, don't show the chip.
!uidObserver.isCallAppVisible
@@ -224,6 +234,8 @@ constructor(
/** Creates the right [OngoingCallModel] based on the call state. */
private fun getOngoingCallModel(): OngoingCallModel {
+ StatusBarChipsModernization.assertInLegacyMode()
+
if (hasOngoingCall()) {
val currentInfo =
callNotificationInfo
@@ -248,6 +260,7 @@ constructor(
startTimeMs = currentInfo.callStartTime,
notificationIconView = icon,
intent = currentInfo.intent,
+ notificationKey = currentInfo.key,
)
} else {
return OngoingCallModel.NoCall
@@ -255,6 +268,8 @@ constructor(
}
override fun addCallback(listener: OngoingCallListener) {
+ StatusBarChipsModernization.assertInLegacyMode()
+
synchronized(mListeners) {
if (!mListeners.contains(listener)) {
mListeners.add(listener)
@@ -263,10 +278,14 @@ constructor(
}
override fun removeCallback(listener: OngoingCallListener) {
+ StatusBarChipsModernization.assertInLegacyMode()
+
synchronized(mListeners) { mListeners.remove(listener) }
}
private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) {
+ StatusBarChipsModernization.assertInLegacyMode()
+
if (notifModel == null) {
logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" })
removeChip()
@@ -310,6 +329,8 @@ constructor(
}
private fun updateChip() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
val currentCallNotificationInfo = callNotificationInfo ?: return
val currentChipView = chipView
@@ -360,6 +381,8 @@ constructor(
}
private fun updateChipClickListener() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
if (Flags.statusBarScreenSharingChips()) {
return
}
@@ -386,10 +409,14 @@ constructor(
/** Returns true if the given [procState] represents a process that's visible to the user. */
private fun isProcessVisibleToUser(procState: Int): Boolean {
+ StatusBarChipsModernization.assertInLegacyMode()
+
return procState <= ActivityManager.PROCESS_STATE_TOP
}
private fun updateGestureListening() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
if (
callNotificationInfo == null ||
callNotificationInfo?.statusBarSwipedAway == true ||
@@ -404,6 +431,8 @@ constructor(
}
private fun removeChip() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
callNotificationInfo = null
if (!Flags.statusBarScreenSharingChips()) {
tearDownChipView()
@@ -432,6 +461,8 @@ constructor(
* detected.
*/
private fun onSwipeAwayGestureDetected() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" })
callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible(
@@ -441,6 +472,8 @@ constructor(
}
private fun sendStateChangeEvent() {
+ StatusBarChipsModernization.assertInLegacyMode()
+
ongoingCallRepository.setOngoingCallState(getOngoingCallModel())
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
index 16d35fe9fa68..2ab2b68bbb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -1,30 +1,29 @@
/*
- * 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.statusbar.notification.shared
+* 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.phone.ongoingcall
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the notifications improved hun animation flag state. */
+/** Helper for reading or using the status_bar_use_interactor_for_call_chip flag state. */
@Suppress("NOTHING_TO_INLINE")
-object NotificationsImprovedHunAnimation {
+object StatusBarChipsModernization {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +32,7 @@ object NotificationsImprovedHunAnimation {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationsImprovedHunAnimation()
+ get() = Flags.statusBarChipsModernization() && Flags.statusBarRootModernization()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -50,4 +49,4 @@ object NotificationsImprovedHunAnimation {
*/
@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/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index f16371ae7e21..b932c71ab426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,7 +34,9 @@ import kotlinx.coroutines.flow.asStateFlow
* [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
* [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two
* classes both refer to this repository.
+ * @deprecated Use [OngoingCallInteractor] instead.
*/
+@Deprecated("Use OngoingCallInteractor instead")
@SysUISingleton
class OngoingCallRepository
@Inject
@@ -49,6 +52,8 @@ constructor(
* [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
*/
fun setOngoingCallState(state: OngoingCallModel) {
+ StatusBarChipsModernization.assertInLegacyMode()
+
logger.log(
TAG,
LogLevel.DEBUG,
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
new file mode 100644
index 000000000000..4b71c0268f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.phone.ongoingcall.domain.interactor
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Interactor for determining whether to show a chip in the status bar for ongoing phone calls.
+ *
+ * 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
+ * */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OngoingCallInteractor @Inject constructor(
+ @Application private val scope: CoroutineScope,
+ activityManagerRepository: ActivityManagerRepository,
+ activeNotificationsInteractor: ActiveNotificationsInteractor,
+ @OngoingCallLog private val logBuffer: LogBuffer,
+) {
+ private val logger = Logger(logBuffer, TAG)
+
+ /**
+ * The current state of ongoing calls.
+ */
+ val ongoingCallState: StateFlow<OngoingCallModel> =
+ activeNotificationsInteractor.ongoingCallNotification
+ .flatMapLatest { notificationModel ->
+ when (notificationModel) {
+ null -> {
+ logger.d("No active call notification - hiding chip")
+ flowOf(OngoingCallModel.NoCall)
+ }
+
+ else -> combine(
+ flowOf(notificationModel),
+ activityManagerRepository.createIsAppVisibleFlow(
+ creationUid = notificationModel.uid,
+ logger = logger,
+ identifyingLogTag = TAG,
+ ),
+ ) { model, isVisible ->
+ when {
+ isVisible -> {
+ logger.d({ "Call app is visible: uid=$int1" }) {
+ int1 = model.uid
+ }
+ OngoingCallModel.InCallWithVisibleApp
+ }
+
+ 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,
+ )
+ }
+ }
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall)
+
+ companion object {
+ private val TAG = "OngoingCall"
+ }
+}
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 34bff80ea919..1a5dcc16f3db 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
@@ -25,6 +25,12 @@ 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.
+ */
+ data object InCallWithVisibleApp : OngoingCallModel
+
+ /**
* There *is* an ongoing call.
*
* @property startTimeMs the time that the phone call started, based on the notification's
@@ -40,5 +46,6 @@ sealed interface OngoingCallModel {
val startTimeMs: Long,
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
+ val notificationKey: String,
) : OngoingCallModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
index b2a0272c06d1..a0cb8298bdb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
@@ -30,13 +30,13 @@ import kotlinx.coroutines.flow.map
@SysUISingleton
class CollapsedStatusBarInteractor
@Inject
-constructor(DisableFlagsInteractor: DisableFlagsInteractor) {
+constructor(disableFlagsInteractor: DisableFlagsInteractor) {
/**
* The visibilities of various status bar child views, based only on the information we received
* from disable flags.
*/
val visibilityViaDisableFlags: Flow<StatusBarDisableFlagsVisibilityModel> =
- DisableFlagsInteractor.disableFlags.map {
+ disableFlagsInteractor.disableFlags.map {
StatusBarDisableFlagsVisibilityModel(
isClockAllowed = it.isClockEnabled,
areNotificationIconsAllowed = it.areNotificationIconsEnabled,
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 72df02714d43..d9b2bd1d66c9 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
@@ -20,9 +20,9 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.view.View
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -31,16 +31,19 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
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
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedDisplaysStatusBarNotificationIconViewStore
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
+import kotlinx.coroutines.launch
/**
* Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel].
@@ -56,6 +59,7 @@ interface HomeStatusBarViewBinder {
* to support the chip animations.
*/
fun bind(
+ displayId: Int,
view: View,
viewModel: HomeStatusBarViewModel,
systemEventChipAnimateIn: ((View) -> Unit)?,
@@ -65,8 +69,13 @@ interface HomeStatusBarViewBinder {
}
@SysUISingleton
-class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinder {
+class HomeStatusBarViewBinderImpl
+@Inject
+constructor(
+ private val viewStoreFactory: ConnectedDisplaysStatusBarNotificationIconViewStore.Factory
+) : HomeStatusBarViewBinder {
override fun bind(
+ displayId: Int,
view: View,
viewModel: HomeStatusBarViewModel,
systemEventChipAnimateIn: ((View) -> Unit)?,
@@ -75,6 +84,14 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
) {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ val iconViewStore =
+ if (StatusBarConnectedDisplays.isEnabled) {
+ viewStoreFactory.create(displayId).also {
+ lifecycleScope.launch { it.activate() }
+ }
+ } else {
+ null
+ }
launch {
viewModel.isTransitioningFromLockscreenToOccluded.collect {
listener.onStatusBarVisibilityMaybeChanged()
@@ -102,7 +119,11 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
view.requireViewById(R.id.ongoing_activity_chip_primary)
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
- OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
+ OngoingActivityChipBinder.bind(
+ primaryChipModel,
+ primaryChipView,
+ iconViewStore,
+ )
if (StatusBarRootModernization.isEnabled) {
when (primaryChipModel) {
is OngoingActivityChipModel.Shown ->
@@ -142,10 +163,18 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
view.requireViewById(R.id.ongoing_activity_chip_secondary)
launch {
viewModel.ongoingActivityChips.collect { chips ->
- OngoingActivityChipBinder.bind(chips.primary, primaryChipView)
+ OngoingActivityChipBinder.bind(
+ chips.primary,
+ primaryChipView,
+ iconViewStore,
+ )
// TODO(b/364653005): Don't show the secondary chip if there isn't
// enough space for it.
- OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
+ OngoingActivityChipBinder.bind(
+ chips.secondary,
+ secondaryChipView,
+ iconViewStore,
+ )
if (StatusBarRootModernization.isEnabled) {
primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
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 1faa9f32af1f..5614d82c2e4d 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.shared.ui.composable
-import android.view.Display
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -42,6 +41,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ui.DarkIconManager
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
@@ -151,12 +151,11 @@ fun StatusBarRoot(
)
iconController.addIconGroup(darkIconManager)
- // TODO(b/372657935): This won't be needed once OngoingCallController is
- // implemented in recommended architecture
- ongoingCallController.setChipView(
- phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
- )
-
+ if (!StatusBarChipsModernization.isEnabled) {
+ ongoingCallController.setChipView(
+ phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
+ }
// For notifications, first inflate the [NotificationIconContainer]
val notificationIconArea =
phoneStatusBarView.requireViewById<ViewGroup>(R.id.notification_icon_area)
@@ -167,22 +166,17 @@ fun StatusBarRoot(
R.id.notificationIcons
)
- // TODO(b/369337701): implement notification icons for all displays.
- // Currently if we try to bind for all displays, there is a crash, because the
- // same notification icon view can't have multiple parents.
- val displayId = context.displayId
- if (displayId == Display.DEFAULT_DISPLAY) {
- scope.launch {
- notificationIconsBinder.bindWhileAttached(
- notificationIconContainer,
- displayId,
- )
- }
+ scope.launch {
+ notificationIconsBinder.bindWhileAttached(
+ notificationIconContainer,
+ context.displayId,
+ )
}
// This binder handles everything else
scope.launch {
statusBarViewBinder.bind(
+ context.displayId,
phoneStatusBarView,
statusBarViewModel,
eventAnimationInteractor::animateStatusBarContentForChipEnter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
index ed0eb6d44508..d3e37119fdcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.model
+import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.service.quicksettings.Tile
@@ -36,6 +37,7 @@ sealed interface InternetTileModel {
val stateDescription: ContentDescription?
val contentDescription: ContentDescription?
+ @SuppressLint("UseCompatLoadingForDrawables")
fun applyTo(state: QSTile.BooleanState, context: Context) {
if (secondaryLabel != null) {
state.secondaryLabel = secondaryLabel.loadText(context)
@@ -50,7 +52,7 @@ sealed interface InternetTileModel {
if (icon != null) {
state.icon = icon
} else if (iconId != null) {
- state.icon = QSTileImpl.ResourceIcon.get(iconId!!)
+ state.icon = QSTileImpl.maybeLoadResourceIcon(iconId!!, context)
}
state.state =
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 6a9b43c995e4..c52275a9eaa4 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
@@ -32,6 +32,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
@@ -39,6 +40,7 @@ import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEvent
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor
@@ -137,6 +139,7 @@ constructor(
collapsedStatusBarInteractor: CollapsedStatusBarInteractor,
private val lightsOutInteractor: LightsOutInteractor,
private val notificationsInteractor: ActiveNotificationsInteractor,
+ headsUpNotificationInteractor: HeadsUpNotificationInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
sceneInteractor: SceneInteractor,
@@ -222,27 +225,43 @@ constructor(
isHomeStatusBarAllowed && !isSecureCameraActive
}
+ private val isAnyChipVisible =
+ if (StatusBarNotifChips.isEnabled) {
+ ongoingActivityChips.map { it.primary is OngoingActivityChipModel.Shown }
+ } else {
+ primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Shown }
+ }
+
override val isClockVisible: Flow<VisibilityModel> =
combine(
shouldHomeStatusBarBeVisible,
+ headsUpNotificationInteractor.showHeadsUpStatusBar,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
- val showClock = shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed
- // TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account.
- VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate)
+ ) { shouldStatusBarBeVisible, showHeadsUp, visibilityViaDisableFlags ->
+ val showClock =
+ shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed && !showHeadsUp
+ // Always use View.INVISIBLE here, so that animations work
+ VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
}
override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
combine(
shouldHomeStatusBarBeVisible,
+ isAnyChipVisible,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
- ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
+ ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
val showNotificationIconContainer =
- shouldStatusBarBeVisible && visibilityViaDisableFlags.areNotificationIconsAllowed
+ if (anyChipVisible) {
+ false
+ } else {
+ shouldStatusBarBeVisible &&
+ visibilityViaDisableFlags.areNotificationIconsAllowed
+ }
VisibilityModel(
- showNotificationIconContainer.toVisibilityInt(),
+ showNotificationIconContainer.toVisibleOrGone(),
visibilityViaDisableFlags.animate,
)
}
+
private val isSystemInfoVisible =
combine(
shouldHomeStatusBarBeVisible,
@@ -250,7 +269,7 @@ constructor(
) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
val showSystemInfo =
shouldStatusBarBeVisible && visibilityViaDisableFlags.isSystemInfoAllowed
- VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
+ VisibilityModel(showSystemInfo.toVisibleOrGone(), visibilityViaDisableFlags.animate)
}
override val systemInfoCombinedVis =
@@ -270,7 +289,11 @@ constructor(
)
@View.Visibility
- private fun Boolean.toVisibilityInt(): Int {
+ private fun Boolean.toVisibleOrGone(): Int {
return if (this) View.VISIBLE else View.GONE
}
+
+ // Similar to the above, but uses INVISIBLE in place of GONE
+ @View.Visibility
+ private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index a72c83e9579c..6ed2ce82b968 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -87,7 +87,11 @@ constructor(
override val lifecycle =
LifecycleRegistry(this).also {
- mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ if (multiuserWifiPickerTrackerSupport()) {
+ mainExecutor.execute { it.currentState = Lifecycle.State.STARTED }
+ } else {
+ mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ }
}
private var wifiPickerTracker: WifiPickerTracker? = null
@@ -178,6 +182,10 @@ constructor(
trySend(new)
}
}
+
+ // If a WifiPicker already exists, call onStop to begin its shutdown
+ // process in preparation for a new one to be created.
+ wifiPickerTracker?.onStop()
wifiPickerTracker =
wifiPickerTrackerFactory
.create(currentContext, lifecycle, callback, "WifiRepository")
@@ -189,17 +197,7 @@ constructor(
// costly and should be avoided whenever possible).
this?.disableScanning()
}
-
- // The lifecycle must be STARTED in order for the callback to receive
- // events.
- mainExecutor.execute {
- lifecycle.currentState = Lifecycle.State.STARTED
- }
- awaitClose {
- mainExecutor.execute {
- lifecycle.currentState = Lifecycle.State.CREATED
- }
- }
+ awaitClose { mainExecutor.execute { wifiPickerTracker?.onStop() } }
}
.stateIn(scope, SharingStarted.Eagerly, current)
}
@@ -275,7 +273,6 @@ constructor(
trySend(new)
}
}
-
wifiPickerTracker =
wifiPickerTrackerFactory
.create(applicationContext, lifecycle, callback, "WifiRepository")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index a3dcc3b6f851..ece5a3facdf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.media.projection.StopReason;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.CastController.Callback;
@@ -26,7 +27,7 @@ public interface CastController extends CallbackController<Callback>, Dumpable {
void setCurrentUserId(int currentUserId);
List<CastDevice> getCastDevices();
void startCasting(CastDevice device);
- void stopCasting(CastDevice device);
+ void stopCasting(CastDevice device, @StopReason int stopReason);
/**
* @return whether we have a connected device.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 52f80fbf50fd..ab208506f203 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -185,13 +185,13 @@ public class CastControllerImpl implements CastController {
}
@Override
- public void stopCasting(CastDevice device) {
+ public void stopCasting(CastDevice device, @StopReason int stopReason) {
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
mLogger.logStopCasting(isProjection);
if (isProjection) {
final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();
if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
- mProjectionManager.stopActiveProjection(StopReason.STOP_QS_TILE);
+ mProjectionManager.stopActiveProjection(stopReason);
} else {
mLogger.logStopCastingNoProjection(projection);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index d210e93e36f1..c03ba0178fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -26,7 +26,12 @@ import java.io.PrintWriter;
/**
* Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
+ *
+ * @deprecated this class is not supported when KEYGUARD_WM_STATE_REFACTOR is enabled.
+ * Use {@link com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor}
+ * or {@link com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor} instead.
*/
+@Deprecated
public interface KeyguardStateController extends CallbackController<Callback>, Dumpable {
/**
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 9839f9d76537..12ed647fdee7 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
@@ -40,12 +40,16 @@ import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import java.time.Duration
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* An interactor that performs business logic related to the status and configuration of Zen Mode
@@ -58,6 +62,7 @@ constructor(
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val iconLoader: ZenIconLoader,
deviceProvisioningRepository: DeviceProvisioningRepository,
userSetupRepository: UserSetupRepository,
@@ -101,13 +106,16 @@ constructor(
/**
* Returns the special "manual DND" mode.
*
- * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
- * specifically; any new or migrated features should use modes more generally, through [modes]
- * or [activeModes].
+ * This should only be used when there is a strong reason to handle DND specifically (such as
+ * legacy UI pieces that haven't been updated to use modes more generally, or if the user
+ * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
+ * other scenarios.
*/
- val dndMode: Flow<ZenMode?> by lazy {
+ val dndMode: StateFlow<ZenMode?> by lazy {
ModesUi.assertInNewMode()
- zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ zenModeRepository.modes
+ .map { modes -> modes.singleOrNull { it.isManualDnd } }
+ .stateIn(scope = backgroundScope, started = SharingStarted.Eagerly, initialValue = null)
}
/** Flow returning the currently active mode(s), if any. */
@@ -201,6 +209,14 @@ constructor(
zenModeRepository.deactivateMode(zenMode)
}
+ fun deactivateAllModes() {
+ for (mode in zenModeRepository.getModes()) {
+ if (mode.isActive) {
+ deactivateMode(mode)
+ }
+ }
+ }
+
private val zenDuration
get() = notificationSettingsRepository.zenDuration.value
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 1c3fece1beb1..28cf78f6777e 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -149,7 +149,8 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private double mContrast = 0.0;
// Theme variant: Vibrant, Tonal, Expressive, etc
@VisibleForTesting
- protected Style mThemeStyle = Style.TONAL_SPOT;
+ @Style.Type
+ protected int mThemeStyle = Style.TONAL_SPOT;
// Accent colors overlay
private FabricatedOverlay mSecondaryOverlay;
// Neutral system colors overlay
@@ -826,15 +827,16 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
}
- private Style fetchThemeStyleFromSetting() {
+ @Style.Type
+ private int fetchThemeStyleFromSetting() {
// Allow-list of Style objects that can be created from a setting string, i.e. can be
// used as a system-wide theme.
// - Content intentionally excluded, intended for media player, not system-wide
- List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
- Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT,
+ @Style.Type List<Integer> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE,
+ Style.SPRITZ, Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT,
Style.MONOCHROMATIC));
- Style style = mThemeStyle;
+ @Style.Type int style = mThemeStyle;
final String overlayPackageJson = mSecureSettings.getStringForUser(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
mUserTracker.getUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index bcf4bad9991d..45fdd21e795e 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -63,7 +63,8 @@ fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Uni
private fun rememberHomeGestureRecognizer(resources: Resources): GestureRecognizer {
val distance =
resources.getDimensionPixelSize(R.dimen.touchpad_tutorial_gestures_distance_threshold)
- return remember(distance) { HomeGestureRecognizer(distance) }
+ val velocity = resources.getDimension(R.dimen.touchpad_home_gesture_velocity_threshold)
+ return remember(distance) { HomeGestureRecognizer(distance, velocity) }
}
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index e10b8253ebad..9801626dac8f 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -19,9 +19,14 @@ package com.android.systemui.touchpad.tutorial.ui.gesture
import android.util.MathUtils
import android.view.MotionEvent
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import kotlin.math.abs
/** Recognizes touchpad home gesture, that is - using three fingers on touchpad - swiping up. */
-class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
+class HomeGestureRecognizer(
+ private val gestureDistanceThresholdPx: Int,
+ private val velocityThresholdPxPerMs: Float,
+ private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
+) : GestureRecognizer {
private val distanceTracker = DistanceTracker()
private var gestureStateChangedCallback: (GestureState) -> Unit = {}
@@ -37,10 +42,14 @@ class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu
override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
val gestureState = distanceTracker.processEvent(event)
+ velocityTracker.accept(event)
updateGestureState(
gestureStateChangedCallback,
gestureState,
- isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
+ isFinished = {
+ -it.deltaY >= gestureDistanceThresholdPx &&
+ abs(velocityTracker.calculateVelocity().value) >= velocityThresholdPxPerMs
+ },
progress = { InProgress(MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx)) },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index c47888609a61..5ff583a19ee9 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -28,10 +28,10 @@ import kotlin.math.abs
class RecentAppsGestureRecognizer(
private val gestureDistanceThresholdPx: Int,
private val velocityThresholdPxPerMs: Float,
- private val distanceTracker: DistanceTracker = DistanceTracker(),
- private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(),
+ private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
) : GestureRecognizer {
+ private val distanceTracker = DistanceTracker()
private var gestureStateChangedCallback: (GestureState) -> Unit = {}
override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
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 5c0cc8150d70..39b434ad65f1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -21,20 +21,24 @@ import android.content.Context
import android.graphics.PixelFormat
import android.os.Bundle
import android.view.MotionEvent
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import com.android.app.tracing.coroutines.coroutineScopeTraced
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.domain.interactor.VolumeDialogVisibilityInteractor
-import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
class VolumeDialog
@Inject
constructor(
@Application context: Context,
- private val viewBinder: VolumeDialogViewBinder,
+ private val componentFactory: VolumeDialogComponent.Factory,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
@@ -64,7 +68,14 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.volume_dialog)
- viewBinder.bind(this)
+ requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+ coroutineScopeTraced("[Volume]dialog") {
+ val component = componentFactory.create(this)
+ with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
+
+ awaitCancellation()
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index b912361ae059..203a157f6ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -16,19 +16,31 @@
package com.android.systemui.volume.dialog
+import android.content.Context
+import android.media.AudioManager
+import com.android.app.tracing.coroutines.coroutineScopeTraced
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.SafetyWarningDialog
import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
import javax.inject.Inject
+import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.suspendCancellableCoroutine
+@OptIn(ExperimentalCoroutinesApi::class)
class VolumeDialogPlugin
@Inject
constructor(
@Application private val applicationCoroutineScope: CoroutineScope,
+ private val context: Context,
+ private val audioManager: AudioManager,
private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
) : VolumeDialog {
@@ -38,17 +50,42 @@ constructor(
override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
job =
applicationCoroutineScope.launch {
- coroutineScope {
+ coroutineScopeTraced("[Volume]plugin") {
pluginComponent =
volumeDialogPluginComponentFactory.create(this).also {
- it.viewModel().launchVolumeDialog()
+ bindPlugin(it.viewModel())
}
}
}
}
+ private fun CoroutineScope.bindPlugin(viewModel: VolumeDialogPluginViewModel) {
+ viewModel.launchVolumeDialog()
+
+ viewModel.isShowingSafetyWarning
+ .mapLatest { isShowingSafetyWarning ->
+ if (isShowingSafetyWarning) {
+ showSafetyWarningVisibility { viewModel.onSafetyWarningDismissed() }
+ }
+ }
+ .launchIn(this)
+ }
+
override fun destroy() {
job?.cancel()
pluginComponent = null
}
+
+ private suspend fun showSafetyWarningVisibility(onDismissed: () -> Unit) =
+ suspendCancellableCoroutine { continuation ->
+ val dialog =
+ object : SafetyWarningDialog(context, audioManager) {
+ override fun cleanUp() {
+ onDismissed()
+ continuation.resume(Unit)
+ }
+ }
+ dialog.show()
+ continuation.invokeOnCancellation { dialog.dismiss() }
+ }
}
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 fb157958a630..434f6b567048 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
@@ -20,6 +20,7 @@ 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
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
import dagger.BindsInstance
import dagger.Subcomponent
import kotlinx.coroutines.CoroutineScope
@@ -32,21 +33,21 @@ import kotlinx.coroutines.CoroutineScope
@Subcomponent(modules = [VolumeDialogModule::class])
interface VolumeDialogComponent {
- /**
- * Provides a coroutine scope to use inside [VolumeDialogScope].
- * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
- * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
- * dialog is not shown.
- */
- @VolumeDialog fun coroutineScope(): CoroutineScope
-
- @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
+ fun volumeDialogViewBinder(): VolumeDialogViewBinder
fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory
@Subcomponent.Factory
interface Factory {
- fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent
+ fun create(
+ /**
+ * Provides a coroutine scope to use inside [VolumeDialogScope].
+ * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this
+ * scope. It's cancelled when the dialog is disposed. This helps to free occupied
+ * resources when volume dialog is not shown.
+ */
+ @BindsInstance @VolumeDialog scope: CoroutineScope
+ ): VolumeDialogComponent
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
new file mode 100644
index 000000000000..f707d6713f9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.volume.dialog.domain.interactor
+
+import android.media.AudioManager
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+private const val VISIBLE_FLAGS = AudioManager.FLAG_SHOW_UI or AudioManager.FLAG_SHOW_UI_WARNINGS
+
+@VolumeDialogPluginScope
+class VolumeDialogSafetyWarningInteractor
+@Inject
+constructor(
+ private val stateInteractor: VolumeDialogStateInteractor,
+ visibilityInteractor: VolumeDialogVisibilityInteractor,
+) {
+
+ val isShowingSafetyWarning: Flow<Boolean> =
+ stateInteractor.volumeDialogState.map {
+ when (it.isShowingSafetyWarning) {
+ is VolumeDialogSafetyWarningModel.Visible ->
+ if (it.isShowingSafetyWarning.flags and VISIBLE_FLAGS == 0) {
+ visibilityInteractor.dialogVisibility.first() is
+ VolumeDialogVisibilityModel.Visible
+ } else {
+ true
+ }
+ is VolumeDialogSafetyWarningModel.Invisible -> false
+ }
+ }
+
+ fun onSafetyWarningDismissed() {
+ stateInteractor.setSafetyWarning(VolumeDialogSafetyWarningModel.Invisible)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
index 5c7289baa401..51e79242daaf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -23,6 +23,7 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.data.repository.VolumeDialogStateRepository
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import javax.inject.Inject
@@ -61,6 +62,9 @@ constructor(
oldState.copy(shouldShowA11ySlider = event.showA11yStream)
}
}
+ is VolumeDialogEventModel.ShowSafetyWarning -> {
+ setSafetyWarning(VolumeDialogSafetyWarningModel.Visible(event.flags))
+ }
else -> {
// do nothing
}
@@ -72,6 +76,10 @@ constructor(
val volumeDialogState: Flow<VolumeDialogStateModel> = volumeDialogStateRepository.state
+ fun setSafetyWarning(model: VolumeDialogSafetyWarningModel) {
+ volumeDialogStateRepository.updateState { it.copy(isShowingSafetyWarning = model) }
+ }
+
/** Returns a copy of [model] filled with the values from [VolumeDialogController.State]. */
private fun VolumeDialogController.State.copyIntoModel(
model: VolumeDialogStateModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
index b83613ba4f8c..40719185e290 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -20,7 +20,6 @@ import android.media.AudioManager
import android.media.AudioManager.RINGER_MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_SILENT
import android.media.AudioManager.RINGER_MODE_VIBRATE
-import android.provider.Settings
import com.android.settingslib.volume.data.repository.AudioSystemRepository
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.plugins.VolumeDialogController
@@ -66,11 +65,6 @@ constructor(
}
},
currentRingerMode = RingerMode(state.ringerModeInternal),
- isEnabled =
- !(state.zenMode == Settings.Global.ZEN_MODE_ALARMS ||
- state.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS ||
- (state.zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS &&
- state.disallowRinger)),
isMuted = it.level == 0 || it.muted,
level = it.level,
levelMax = it.levelMax,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
index 3c24e02f3732..84a82805aace 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt
@@ -23,8 +23,6 @@ data class VolumeDialogRingerModel(
val availableModes: List<RingerMode>,
/** Current ringer mode internal */
val currentRingerMode: RingerMode,
- /** whether the ringer is allowed given the current ZenMode */
- val isEnabled: Boolean,
/** Whether the current ring stream level is zero or the controller state is muted */
val isMuted: Boolean,
/** Ring stream level */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 1963ba22d444..9eee91beda51 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.dialog.ringer.ui.binder
+import android.animation.ArgbEvaluator
+import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageButton
@@ -23,128 +25,285 @@ import androidx.annotation.LayoutRes
import androidx.compose.ui.util.fastForEachIndexed
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.R as internalR
import com.android.settingslib.Utils
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.util.children
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.util.VolumeDialogRingerDrawerTransitionListener
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonUiModel
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
+import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import javax.inject.Inject
+import kotlin.properties.Delegates
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+private const val CLOSE_DRAWER_DELAY = 300L
@VolumeDialogScope
class VolumeDialogRingerViewBinder
@Inject
-constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
+ private val roundnessSpringForce =
+ SpringForce(0F).apply {
+ stiffness = 800F
+ dampingRatio = 0.6F
+ }
+ private val colorSpringForce =
+ SpringForce(0F).apply {
+ stiffness = 3800F
+ dampingRatio = 1F
+ }
+ private val rgbEvaluator = ArgbEvaluator()
- fun bind(view: View) {
- with(view) {
- val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background)
- val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
- repeatWhenAttached {
- viewModel(
- traceName = "VolumeDialogRingerViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
- ) { viewModel ->
- viewModel.ringerViewModel
- .onEach { ringerState ->
- when (ringerState) {
- is RingerViewModelState.Available -> {
- val uiModel = ringerState.uiModel
+ fun CoroutineScope.bind(view: View) {
+ val volumeDialogBackgroundView = view.requireViewById<View>(R.id.volume_dialog_background)
+ val drawerContainer = view.requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
+ val unselectedButtonUiModel = RingerButtonUiModel.getUnselectedButton(view.context)
+ val selectedButtonUiModel = RingerButtonUiModel.getSelectedButton(view.context)
+ val volumeDialogBgSmallRadius =
+ view.context.resources.getDimensionPixelSize(
+ R.dimen.volume_dialog_background_square_corner_radius
+ )
+ val volumeDialogBgFullRadius =
+ view.context.resources.getDimensionPixelSize(
+ R.dimen.volume_dialog_background_corner_radius
+ )
+ var backgroundAnimationProgress: Float by
+ Delegates.observable(0F) { _, _, progress ->
+ volumeDialogBackgroundView.applyCorners(
+ fullRadius = volumeDialogBgFullRadius,
+ diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius,
+ progress,
+ )
+ }
+ val ringerDrawerTransitionListener = VolumeDialogRingerDrawerTransitionListener {
+ backgroundAnimationProgress = it
+ }
+ drawerContainer.setTransitionListener(ringerDrawerTransitionListener)
+ volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate()
+ viewModel.ringerViewModel
+ .onEach { ringerState ->
+ when (ringerState) {
+ is RingerViewModelState.Available -> {
+ val uiModel = ringerState.uiModel
- bindDrawerButtons(viewModel, uiModel)
+ // Set up view background and visibility
+ drawerContainer.visibility = View.VISIBLE
+ when (uiModel.drawerState) {
+ is RingerDrawerState.Initial -> {
+ drawerContainer.animateAndBindDrawerButtons(
+ viewModel,
+ uiModel,
+ selectedButtonUiModel,
+ unselectedButtonUiModel,
+ )
+ ringerDrawerTransitionListener.setProgressChangeEnabled(true)
+ drawerContainer.closeDrawer(uiModel.currentButtonIndex)
+ }
- // Set up view background and visibility
- drawerContainer.visibility = View.VISIBLE
- when (uiModel.drawerState) {
- is RingerDrawerState.Initial -> {
- drawerContainer.closeDrawer(uiModel.currentButtonIndex)
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background
+ is RingerDrawerState.Closed -> {
+ if (
+ uiModel.selectedButton.ringerMode ==
+ uiModel.drawerState.currentMode
+ ) {
+ drawerContainer.animateAndBindDrawerButtons(
+ viewModel,
+ uiModel,
+ selectedButtonUiModel,
+ unselectedButtonUiModel,
+ onProgressChanged = { progress, isReverse ->
+ // Let's make button progress when switching matches
+ // motionLayout transition progress. When full radius,
+ // progress is 0.0. When small radius, progress is 1.0.
+ backgroundAnimationProgress =
+ if (isReverse) {
+ 1F - progress
+ } else {
+ progress
+ }
+ },
+ ) {
+ if (
+ uiModel.currentButtonIndex ==
+ uiModel.availableButtons.size - 1
+ ) {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(
+ false
)
- }
- is RingerDrawerState.Closed -> {
- drawerContainer.closeDrawer(uiModel.currentButtonIndex)
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background
+ } else {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(
+ true
)
}
- is RingerDrawerState.Open -> {
- // Open drawer
- drawerContainer.transitionToEnd()
- if (
- uiModel.currentButtonIndex !=
- uiModel.availableButtons.size - 1
- ) {
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background_small_radius
- )
- }
- }
+ drawerContainer.closeDrawer(uiModel.currentButtonIndex)
}
}
- is RingerViewModelState.Unavailable -> {
- drawerContainer.visibility = View.GONE
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background
- )
+ }
+
+ is RingerDrawerState.Open -> {
+ drawerContainer.animateAndBindDrawerButtons(
+ viewModel,
+ uiModel,
+ selectedButtonUiModel,
+ unselectedButtonUiModel,
+ )
+ // Open drawer
+ if (
+ uiModel.currentButtonIndex == uiModel.availableButtons.size - 1
+ ) {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(false)
+ } else {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(true)
}
+ drawerContainer.transitionToState(
+ R.id.volume_dialog_ringer_drawer_open
+ )
+ volumeDialogBackgroundView.background =
+ volumeDialogBackgroundView.background.mutate()
}
}
- .launchIn(this)
+ }
+
+ is RingerViewModelState.Unavailable -> {
+ drawerContainer.visibility = View.GONE
+ volumeDialogBackgroundView.setBackgroundResource(
+ R.drawable.volume_dialog_background
+ )
+ }
}
}
+ .launchIn(this)
+ }
+
+ private suspend fun MotionLayout.animateAndBindDrawerButtons(
+ viewModel: VolumeDialogRingerDrawerViewModel,
+ uiModel: RingerViewModel,
+ selectedButtonUiModel: RingerButtonUiModel,
+ unselectedButtonUiModel: RingerButtonUiModel,
+ onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> },
+ onAnimationEnd: Runnable? = null,
+ ) {
+ ensureChildCount(R.layout.volume_ringer_button, uiModel.availableButtons.size)
+ if (
+ uiModel.drawerState is RingerDrawerState.Closed &&
+ uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
+ ) {
+ val count = uiModel.availableButtons.size
+ val selectedButton =
+ getChildAt(count - uiModel.currentButtonIndex - 1)
+ .requireViewById<ImageButton>(R.id.volume_drawer_button)
+ val previousIndex =
+ uiModel.availableButtons.indexOfFirst {
+ it?.ringerMode == uiModel.drawerState.previousMode
+ }
+ val unselectedButton =
+ getChildAt(count - previousIndex - 1)
+ .requireViewById<ImageButton>(R.id.volume_drawer_button)
+
+ // On roundness animation end.
+ val roundnessAnimationEndListener =
+ DynamicAnimation.OnAnimationEndListener { _, _, _, _ ->
+ postDelayed(
+ { bindButtons(viewModel, uiModel, onAnimationEnd, isAnimated = true) },
+ CLOSE_DRAWER_DELAY,
+ )
+ }
+ // We only need to execute on roundness animation end and volume dialog background
+ // progress update once because these changes should be applied once on volume dialog
+ // background and ringer drawer views.
+ selectedButton.animateTo(
+ selectedButtonUiModel,
+ if (uiModel.currentButtonIndex == count - 1) {
+ onProgressChanged
+ } else {
+ { _, _ -> }
+ },
+ roundnessAnimationEndListener,
+ )
+ unselectedButton.animateTo(
+ unselectedButtonUiModel,
+ if (previousIndex == count - 1) {
+ onProgressChanged
+ } else {
+ { _, _ -> }
+ },
+ )
+ } else {
+ bindButtons(viewModel, uiModel, onAnimationEnd)
}
}
- private fun View.bindDrawerButtons(
+ private fun MotionLayout.bindButtons(
viewModel: VolumeDialogRingerDrawerViewModel,
uiModel: RingerViewModel,
+ onAnimationEnd: Runnable? = null,
+ isAnimated: Boolean = false,
) {
- val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
val count = uiModel.availableButtons.size
- drawerContainer.ensureChildCount(R.layout.volume_ringer_button, count)
-
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
ringerButton?.let {
- val view = drawerContainer.getChildAt(count - index - 1)
- // TODO (b/369995871): object animator for button switch ( active <-> inactive )
+ val view = getChildAt(count - index - 1)
+ val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
- view.bindDrawerButton(uiModel.selectedButton, viewModel, isSelected = true)
+ view.bindDrawerButton(
+ if (isOpen) it else uiModel.selectedButton,
+ viewModel,
+ isOpen,
+ isSelected = true,
+ isAnimated = isAnimated,
+ )
} else {
- view.bindDrawerButton(it, viewModel)
+ view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
}
}
}
+ onAnimationEnd?.run()
}
private fun View.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
+ isOpen: Boolean,
isSelected: Boolean = false,
+ isAnimated: Boolean = false,
) {
+ val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
setImageResource(buttonViewModel.imageResId)
- contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
- if (isSelected) {
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
+ }
+ if (isSelected && !isAnimated) {
setBackgroundResource(R.drawable.volume_drawer_selection_bg)
setColorFilter(
Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnPrimary)
)
- } else {
+ background = background.mutate()
+ } else if (!isAnimated) {
setBackgroundResource(R.drawable.volume_ringer_item_bg)
setColorFilter(
Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnSurface)
)
+ background = background.mutate()
}
setOnClickListener {
viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
@@ -171,9 +330,10 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact
}
private fun MotionLayout.closeDrawer(selectedIndex: Int) {
+ setTransition(R.id.close_to_open_transition)
cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close)
.adjustClosedConstraintsForDrawer(selectedIndex, this)
- transitionToStart()
+ transitionToState(R.id.volume_dialog_ringer_drawer_close)
}
private fun ConstraintSet.adjustOpenConstraintsForDrawer(motionLayout: MotionLayout) {
@@ -263,4 +423,54 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact
connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START)
connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END)
}
+
+ private suspend fun ImageButton.animateTo(
+ ringerButtonUiModel: RingerButtonUiModel,
+ onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> },
+ roundnessAnimationEndListener: DynamicAnimation.OnAnimationEndListener? = null,
+ ) {
+ val roundnessAnimation =
+ SpringAnimation(FloatValueHolder(0F)).setSpring(roundnessSpringForce)
+ val colorAnimation = SpringAnimation(FloatValueHolder(0F)).setSpring(colorSpringForce)
+ val radius = (background as GradientDrawable).cornerRadius
+ val cornerRadiusDiff =
+ ringerButtonUiModel.cornerRadius - (background as GradientDrawable).cornerRadius
+ val roundnessAnimationUpdateListener =
+ DynamicAnimation.OnAnimationUpdateListener { _, value, _ ->
+ onProgressChanged(value, cornerRadiusDiff > 0F)
+ (background as GradientDrawable).cornerRadius = radius + value * cornerRadiusDiff
+ background.invalidateSelf()
+ }
+ val colorAnimationUpdateListener =
+ DynamicAnimation.OnAnimationUpdateListener { _, value, _ ->
+ val currentIconColor =
+ rgbEvaluator.evaluate(
+ value.coerceIn(0F, 1F),
+ imageTintList?.colors?.first(),
+ ringerButtonUiModel.tintColor,
+ ) as Int
+ val currentBgColor =
+ rgbEvaluator.evaluate(
+ value.coerceIn(0F, 1F),
+ (background as GradientDrawable).color?.colors?.get(0),
+ ringerButtonUiModel.backgroundColor,
+ ) as Int
+
+ (background as GradientDrawable).setColor(currentBgColor)
+ background.invalidateSelf()
+ setColorFilter(currentIconColor)
+ }
+ coroutineScope {
+ launch { colorAnimation.suspendAnimate(colorAnimationUpdateListener) }
+ roundnessAnimation.suspendAnimate(
+ roundnessAnimationUpdateListener,
+ roundnessAnimationEndListener,
+ )
+ }
+ }
+
+ private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float) {
+ (background as GradientDrawable).cornerRadius = fullRadius - progress * diff
+ background.invalidateSelf()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt
new file mode 100644
index 000000000000..6e3db0afb483
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.volume.dialog.ringer.ui.util
+
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+class VolumeDialogRingerDrawerTransitionListener(private val onProgressChanged: (Float) -> Unit) :
+ MotionLayout.TransitionListener {
+
+ private var notifyProgressChangeEnabled = true
+
+ fun setProgressChangeEnabled(enabled: Boolean) {
+ notifyProgressChangeEnabled = enabled
+ }
+
+ override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {}
+
+ override fun onTransitionChange(
+ motionLayout: MotionLayout?,
+ startId: Int,
+ endId: Int,
+ progress: Float,
+ ) {
+ if (notifyProgressChangeEnabled) {
+ onProgressChanged(progress)
+ }
+ }
+
+ override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {}
+
+ override fun onTransitionTrigger(
+ motionLayout: MotionLayout?,
+ triggerId: Int,
+ positive: Boolean,
+ progress: Float,
+ ) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt
new file mode 100644
index 000000000000..3c465674ebb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.volume.dialog.ringer.ui.viewmodel
+
+import android.content.Context
+import com.android.internal.R as internalR
+import com.android.settingslib.Utils
+import com.android.systemui.res.R
+
+/** Models the UI state of ringer button */
+data class RingerButtonUiModel(
+ /** Icon color. */
+ val tintColor: Int,
+ val backgroundColor: Int,
+ val cornerRadius: Int,
+) {
+ companion object {
+ fun getUnselectedButton(context: Context): RingerButtonUiModel {
+ return RingerButtonUiModel(
+ tintColor =
+ Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnSurface),
+ backgroundColor =
+ Utils.getColorAttrDefaultColor(
+ context,
+ internalR.attr.materialColorSurfaceContainerHighest,
+ ),
+ cornerRadius =
+ context.resources.getDimensionPixelSize(
+ R.dimen.volume_dialog_background_square_corner_radius
+ ),
+ )
+ }
+
+ fun getSelectedButton(context: Context): RingerButtonUiModel {
+ return RingerButtonUiModel(
+ tintColor =
+ Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnPrimary),
+ backgroundColor =
+ Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorPrimary),
+ cornerRadius =
+ context.resources.getDimensionPixelSize(
+ R.dimen.volume_dialog_ringer_selected_button_background_radius
+ ),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
index f3218370c4dd..afb3f68e519e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
@@ -25,7 +25,8 @@ sealed interface RingerDrawerState {
data class Open(val mode: RingerMode) : RingerDrawerState
/** When clicked to close drawer */
- data class Closed(val mode: RingerMode) : RingerDrawerState
+ data class Closed(val currentMode: RingerMode, val previousMode: RingerMode) :
+ RingerDrawerState
/** Initial state when volume dialog is shown with a closed drawer. */
interface Initial : RingerDrawerState {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index 624dcc71e2a9..627d75ee108d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -21,10 +21,13 @@ import android.media.AudioAttributes
import android.media.AudioManager.RINGER_MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_SILENT
import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
import android.os.VibrationEffect
import android.widget.Toast
import com.android.internal.R as internalR
import com.android.settingslib.Utils
+import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -32,12 +35,12 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -50,13 +53,15 @@ import kotlinx.coroutines.launch
private const val SHOW_RINGER_TOAST_COUNT = 12
+@VolumeDialogScope
class VolumeDialogRingerDrawerViewModel
-@AssistedInject
+@Inject
constructor(
@Application private val applicationContext: Context,
@VolumeDialog private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val interactor: VolumeDialogRingerInteractor,
+ soundPolicyInteractor: NotificationsSoundPolicyInteractor,
+ private val ringerInteractor: VolumeDialogRingerInteractor,
private val vibrator: VibratorHelper,
private val volumeDialogLogger: VolumeDialogLogger,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@@ -65,10 +70,14 @@ constructor(
private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
val ringerViewModel: StateFlow<RingerViewModelState> =
- combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+ combine(
+ soundPolicyInteractor.isZenMuted(AudioStream(STREAM_RING)),
+ ringerInteractor.ringerModel,
+ drawerState,
+ ) { isZenMuted, ringerModel, state ->
level = ringerModel.level
levelMax = ringerModel.levelMax
- ringerModel.toViewModel(state)
+ ringerModel.toViewModel(state, isZenMuted)
}
.flowOn(backgroundDispatcher)
.stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable)
@@ -89,7 +98,7 @@ constructor(
Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
provideTouchFeedback(ringerMode)
maybeShowToast(ringerMode)
- interactor.setRingerMode(ringerMode)
+ ringerInteractor.setRingerMode(ringerMode)
}
visibilityInteractor.resetDismissTimeout()
drawerState.value =
@@ -98,7 +107,10 @@ constructor(
RingerDrawerState.Open(ringerMode)
}
is RingerDrawerState.Open -> {
- RingerDrawerState.Closed(ringerMode)
+ RingerDrawerState.Closed(
+ ringerMode,
+ (drawerState.value as RingerDrawerState.Open).mode,
+ )
}
is RingerDrawerState.Closed -> {
RingerDrawerState.Open(ringerMode)
@@ -109,7 +121,7 @@ constructor(
private fun provideTouchFeedback(ringerMode: RingerMode) {
when (ringerMode.value) {
RINGER_MODE_NORMAL -> {
- interactor.scheduleTouchFeedback()
+ ringerInteractor.scheduleTouchFeedback()
null
}
RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
@@ -119,7 +131,8 @@ constructor(
}
private fun VolumeDialogRingerModel.toViewModel(
- drawerState: RingerDrawerState
+ drawerState: RingerDrawerState,
+ isZenMuted: Boolean,
): RingerViewModelState {
val currentIndex = availableModes.indexOf(currentRingerMode)
if (currentIndex == -1) {
@@ -128,10 +141,11 @@ constructor(
return if (currentIndex == -1 || isSingleVolume) {
RingerViewModelState.Unavailable
} else {
- toButtonViewModel(currentRingerMode, isSelectedButton = true)?.let {
+ toButtonViewModel(currentRingerMode, isZenMuted, isSelectedButton = true)?.let {
RingerViewModelState.Available(
RingerViewModel(
- availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+ availableButtons =
+ availableModes.map { mode -> toButtonViewModel(mode, isZenMuted) },
currentButtonIndex = currentIndex,
selectedButton = it,
drawerState = drawerState,
@@ -143,6 +157,7 @@ constructor(
private fun VolumeDialogRingerModel.toButtonViewModel(
ringerMode: RingerMode,
+ isZenMuted: Boolean,
isSelectedButton: Boolean = false,
): RingerButtonViewModel? {
return when (ringerMode.value) {
@@ -172,7 +187,7 @@ constructor(
)
RINGER_MODE_NORMAL ->
when {
- isMuted && isEnabled ->
+ isMuted && !isZenMuted ->
RingerButtonViewModel(
imageResId =
if (isSelectedButton) {
@@ -222,7 +237,7 @@ constructor(
private fun maybeShowToast(ringerMode: RingerMode) {
coroutineScope.launch {
- val seenToastCount = interactor.getToastCount()
+ val seenToastCount = ringerInteractor.getToastCount()
if (seenToastCount > SHOW_RINGER_TOAST_COUNT) {
return@launch
}
@@ -256,12 +271,7 @@ constructor(
)
}
toastText?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT).show() }
- interactor.updateToastCount(seenToastCount)
+ ringerInteractor.updateToastCount(seenToastCount)
}
}
-
- @AssistedFactory
- interface Factory {
- fun create(): VolumeDialogRingerDrawerViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt
new file mode 100644
index 000000000000..39fc222860c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.dialog.shared.model
+
+/** Models current Volume Safety Dialog state. */
+sealed interface VolumeDialogSafetyWarningModel {
+
+ /** Volume Safety Dialog is visible and has been shown with the [flags]. */
+ data class Visible(val flags: Int) : VolumeDialogSafetyWarningModel
+
+ /** Volume Safety Dialog is invisible. */
+ data object Invisible : VolumeDialogSafetyWarningModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
index 1792b9967681..838006d0adb2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
@@ -21,6 +21,8 @@ import android.content.ComponentName
/** Models a state of the Volume Dialog. */
data class VolumeDialogStateModel(
val shouldShowA11ySlider: Boolean = false,
+ val isShowingSafetyWarning: VolumeDialogSafetyWarningModel =
+ VolumeDialogSafetyWarningModel.Invisible,
val streamModels: Map<Int, VolumeDialogStreamModel> = mapOf(),
val ringerModeInternal: Int = 0,
val ringerModeExternal: Int = 0,
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 772ae7736cad..c0c525bcb37d 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
@@ -17,6 +17,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.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.BindsInstance
@@ -34,6 +35,8 @@ interface VolumeDialogSliderComponent {
fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder
+ fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
+
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
new file mode 100644
index 000000000000..5a7fbc6341f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.haptics.slider.HapticSlider
+import com.android.systemui.haptics.slider.HapticSliderPlugin
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
+import com.google.android.material.slider.Slider
+import com.google.android.msdl.domain.MSDLPlayer
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogSliderHapticsViewBinder
+@Inject
+constructor(
+ private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val msdlPlayer: MSDLPlayer,
+ private val systemClock: SystemClock,
+) {
+
+ fun CoroutineScope.bind(view: View) {
+ val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider)
+ val hapticSliderPlugin =
+ HapticSliderPlugin(
+ slider = HapticSlider.Slider(sliderView),
+ vibratorHelper = vibratorHelper,
+ msdlPlayer = msdlPlayer,
+ systemClock = systemClock,
+ )
+ hapticSliderPlugin.startInScope(this)
+
+ sliderView.addOnChangeListener { _, value, fromUser ->
+ hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser)
+ }
+ sliderView.addOnSliderTouchListener(
+ object : Slider.OnSliderTouchListener {
+
+ override fun onStartTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStartTrackingTouch()
+ }
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStopTrackingTouch()
+ }
+ }
+ )
+
+ inputEventsViewModel.event
+ .onEach {
+ when (it) {
+ is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown()
+ is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event)
+ }
+ }
+ .launchIn(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
index 7fd177d55d76..4ecac7a81893 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
@@ -18,34 +18,23 @@ package com.android.systemui.volume.dialog.sliders.ui
import android.annotation.SuppressLint
import android.view.View
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderTouchesViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
import com.google.android.material.slider.Slider
import javax.inject.Inject
@VolumeDialogSliderScope
class VolumeDialogSliderTouchesViewBinder
@Inject
-constructor(private val viewModelFactory: VolumeDialogSliderTouchesViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) {
@SuppressLint("ClickableViewAccessibility")
fun bind(view: View) {
with(view.requireViewById<Slider>(R.id.volume_dialog_slider)) {
- repeatWhenAttached {
- viewModel(
- traceName = "VolumeDialogSliderTouchesViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
- ) { viewModel ->
- setOnTouchListener { _, event ->
- viewModel.onTouchEvent(event)
- false
- }
- }
+ setOnTouchListener { _, event ->
+ viewModel.onTouchEvent(event)
+ false
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 1c231b521bae..f30524638150 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -18,14 +18,12 @@ package com.android.systemui.volume.dialog.sliders.ui
import android.animation.Animator
import android.animation.ObjectAnimator
+import android.annotation.SuppressLint
import android.view.View
import android.view.animation.DecelerateInterpolator
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
-import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
@@ -33,7 +31,7 @@ import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import javax.inject.Inject
import kotlin.math.roundToInt
-import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -43,45 +41,35 @@ private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
class VolumeDialogSliderViewBinder
@Inject
constructor(
- private val viewModelFactory: VolumeDialogSliderViewModel.Factory,
+ private val viewModel: VolumeDialogSliderViewModel,
private val jankListenerFactory: JankListenerFactory,
) {
- fun bind(view: View) {
- with(view) {
- val sliderView: Slider =
- requireViewById<Slider>(R.id.volume_dialog_slider).apply {
- labelBehavior = LabelFormatter.LABEL_GONE
- }
- repeatWhenAttached {
- viewModel(
- traceName = "VolumeDialogSliderViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
- ) { viewModel ->
- sliderView.addOnChangeListener { _, value, fromUser ->
- viewModel.setStreamVolume(value.roundToInt(), fromUser)
- }
-
- viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
-
- awaitCancellation()
- }
+ fun CoroutineScope.bind(view: View) {
+ val sliderView: Slider =
+ view.requireViewById<Slider>(R.id.volume_dialog_slider).apply {
+ labelBehavior = LabelFormatter.LABEL_GONE
+ trackIconActiveColor = trackInactiveTintList
}
+ sliderView.addOnChangeListener { _, value, fromUser ->
+ viewModel.setStreamVolume(value.roundToInt(), fromUser)
}
+
+ viewModel.state.onEach { it.bindToSlider(sliderView) }.launchIn(this)
}
- private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
- slider.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> }
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private suspend fun VolumeDialogSliderStateModel.bindToSlider(slider: Slider) {
with(slider) {
- valueFrom = levelMin.toFloat()
- valueTo = levelMax.toFloat()
+ valueFrom = minValue
+ valueTo = maxValue
// coerce the current value to the new value range before animating it
value = value.coerceIn(valueFrom, valueTo)
setValueAnimated(
- level.toFloat(),
+ value,
jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
)
+ trackIconActiveEnd = context.getDrawable(iconRes)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 1b2b4fff5b8e..c9b525930ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -21,58 +21,48 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.compose.ui.util.fastForEachIndexed
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@VolumeDialogScope
class VolumeDialogSlidersViewBinder
@Inject
-constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSlidersViewModel) {
- fun bind(view: View) {
- with(view) {
- val floatingSlidersContainer: ViewGroup =
- requireViewById(R.id.volume_dialog_floating_sliders_container)
- val mainSliderContainer: View =
- requireViewById(R.id.volume_dialog_main_slider_container)
- repeatWhenAttached {
- viewModel(
- traceName = "VolumeDialogSlidersViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
- ) { viewModel ->
- viewModel.sliders
- .onEach { uiModel ->
- uiModel.sliderComponent.bindSlider(mainSliderContainer)
+ fun CoroutineScope.bind(view: View) {
+ val floatingSlidersContainer: ViewGroup =
+ view.requireViewById(R.id.volume_dialog_floating_sliders_container)
+ val mainSliderContainer: View =
+ view.requireViewById(R.id.volume_dialog_main_slider_container)
+ viewModel.sliders
+ .onEach { uiModel ->
+ bindSlider(uiModel.sliderComponent, mainSliderContainer)
- val floatingSliderViewBinders = uiModel.floatingSliderComponent
- floatingSlidersContainer.ensureChildCount(
- viewLayoutId = R.layout.volume_dialog_slider_floating,
- count = floatingSliderViewBinders.size,
- )
- floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
- sliderComponent.bindSlider(
- floatingSlidersContainer.getChildAt(index)
- )
- }
- }
- .launchIn(this)
+ val floatingSliderViewBinders = uiModel.floatingSliderComponent
+ floatingSlidersContainer.ensureChildCount(
+ viewLayoutId = R.layout.volume_dialog_slider_floating,
+ count = floatingSliderViewBinders.size,
+ )
+ floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
+ bindSlider(sliderComponent, floatingSlidersContainer.getChildAt(index))
}
}
- }
+ .launchIn(this)
}
- private fun VolumeDialogSliderComponent.bindSlider(sliderContainer: View) {
- sliderViewBinder().bind(sliderContainer)
- sliderTouchesViewBinder().bind(sliderContainer)
+ private fun CoroutineScope.bindSlider(
+ component: VolumeDialogSliderComponent,
+ sliderContainer: View,
+ ) {
+ with(component.sliderViewBinder()) { bind(sliderContainer) }
+ with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
+ with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
new file mode 100644
index 000000000000..5c39b6f9359c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.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.volume.dialog.sliders.ui.viewmodel
+
+import android.media.AudioManager
+import androidx.annotation.DrawableRes
+import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+
+class VolumeDialogSliderIconProvider
+@Inject
+constructor(
+ private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+ private val audioVolumeInteractor: AudioVolumeInteractor,
+) {
+
+ @DrawableRes
+ fun getStreamIcon(
+ stream: Int,
+ level: Int,
+ levelMin: Int,
+ levelMax: Int,
+ isMuted: Boolean,
+ isRoutedToBluetooth: Boolean,
+ ): Flow<Int> {
+ return combine(
+ notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)),
+ ringerModeForStream(stream),
+ ) { isZenMuted, ringerMode ->
+ val isStreamOffline = level == 0 || isMuted
+ if (isZenMuted) {
+ // TODO(b/372466264) use icon for the corresponding zenmode
+ return@combine com.android.internal.R.drawable.ic_qs_dnd
+ }
+ when (ringerMode?.value) {
+ AudioManager.RINGER_MODE_VIBRATE ->
+ return@combine R.drawable.ic_volume_ringer_vibrate
+ AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off
+ }
+ if (isRoutedToBluetooth) {
+ return@combine if (stream == AudioManager.STREAM_VOICE_CALL) {
+ R.drawable.ic_volume_bt_sco
+ } else {
+ if (isStreamOffline) {
+ R.drawable.ic_volume_media_bt_mute
+ } else {
+ R.drawable.ic_volume_media_bt
+ }
+ }
+ }
+
+ return@combine if (isStreamOffline) {
+ getMutedIconForStream(stream) ?: getIconForStream(stream)
+ } else {
+ if (level < (levelMax + levelMin) / 2) {
+ // This icon is different on TV
+ R.drawable.ic_volume_media_low
+ } else {
+ getIconForStream(stream)
+ }
+ }
+ }
+ }
+
+ @DrawableRes
+ private fun getMutedIconForStream(stream: Int): Int? {
+ return when (stream) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
+ AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
+ AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
+ else -> null
+ }
+ }
+
+ @DrawableRes
+ private fun getIconForStream(stream: Int): Int {
+ return when (stream) {
+ AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media
+ AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
+ AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
+ AudioManager.STREAM_ALARM -> R.drawable.ic_alarm
+ AudioManager.STREAM_VOICE_CALL -> com.android.internal.R.drawable.ic_phone
+ AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system
+ else -> error("Unsupported stream: $stream")
+ }
+ }
+
+ /**
+ * Emits [RingerMode] for the [stream] if it's affecting it and null when [RingerMode] doesn't
+ * affect the [stream]
+ */
+ private fun ringerModeForStream(stream: Int): Flow<RingerMode?> {
+ return if (stream == AudioManager.STREAM_RING) {
+ audioVolumeInteractor.ringerMode
+ } else {
+ flowOf(null)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
index 144c75da2b2b..755776ac9723 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
@@ -17,20 +17,27 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
-class VolumeDialogSliderTouchesViewModel
-@AssistedInject
-constructor(private val interactor: VolumeDialogSliderInputEventsInteractor) {
+@VolumeDialogSliderScope
+class VolumeDialogSliderInputEventsViewModel
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val interactor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+ val event =
+ interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull()
fun onTouchEvent(event: MotionEvent) {
interactor.onTouchEvent(event)
}
-
- @AssistedFactory
- interface Factory {
- fun create(): VolumeDialogSliderTouchesViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
new file mode 100644
index 000000000000..5750c049082f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.volume.dialog.sliders.ui.viewmodel
+
+import androidx.annotation.DrawableRes
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+
+data class VolumeDialogSliderStateModel(
+ val minValue: Float,
+ val maxValue: Float,
+ val value: Float,
+ @DrawableRes val iconRes: Int,
+)
+
+fun VolumeDialogStreamModel.toStateModel(@DrawableRes iconRes: Int): VolumeDialogSliderStateModel {
+ return VolumeDialogSliderStateModel(
+ minValue = levelMin.toFloat(),
+ value = level.toFloat(),
+ maxValue = levelMax.toFloat(),
+ iconRes = iconRes,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index cf04d45d54ff..2d5652420ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -21,9 +21,9 @@ import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -32,7 +32,9 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -49,18 +51,19 @@ import kotlinx.coroutines.flow.stateIn
private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogSliderScope
class VolumeDialogSliderViewModel
-@AssistedInject
+@Inject
constructor(
private val interactor: VolumeDialogSliderInteractor,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@VolumeDialog private val coroutineScope: CoroutineScope,
+ private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
private val systemClock: SystemClock,
) {
private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
-
- val model: Flow<VolumeDialogStreamModel> =
+ private val model: Flow<VolumeDialogStreamModel> =
interactor.slider
.filter {
val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
@@ -69,6 +72,21 @@ constructor(
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
+ val state: Flow<VolumeDialogSliderStateModel> =
+ model.flatMapLatest { streamModel ->
+ with(streamModel) {
+ volumeDialogSliderIconProvider.getStreamIcon(
+ stream = stream,
+ level = level,
+ levelMin = levelMin,
+ levelMax = levelMax,
+ isMuted = muted,
+ isRoutedToBluetooth = routedToBluetooth,
+ )
+ }
+ .map { icon -> streamModel.toStateModel(icon) }
+ }
+
init {
userVolumeUpdates
.filterNotNull()
@@ -90,10 +108,4 @@ constructor(
private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
-
- @AssistedFactory
- interface Factory {
-
- fun create(): VolumeDialogSliderViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index d1972231d373..d8e6aec026c6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -17,10 +17,10 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -28,8 +28,9 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+@VolumeDialogScope
class VolumeDialogSlidersViewModel
-@AssistedInject
+@Inject
constructor(
@VolumeDialog coroutineScope: CoroutineScope,
private val slidersInteractor: VolumeDialogSlidersInteractor,
@@ -47,12 +48,6 @@ constructor(
}
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
-
- @AssistedFactory
- interface Factory {
-
- fun create(): VolumeDialogSlidersViewModel
- }
}
/** Models slider ui */
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 f6c1743a4bea..a3166a9978f4 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
@@ -25,9 +25,6 @@ import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
import androidx.constraintlayout.motion.widget.MotionLayout
import com.android.internal.view.RotationPolicy
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.util.children
import com.android.systemui.volume.SystemUIInterpolators
@@ -44,7 +41,6 @@ import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
@@ -61,7 +57,7 @@ class VolumeDialogViewBinder
@Inject
constructor(
private val volumeResources: VolumeDialogResources,
- private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
+ private val viewModel: VolumeDialogViewModel,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
@@ -69,35 +65,27 @@ constructor(
private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
) {
- fun bind(dialog: Dialog) {
+ fun CoroutineScope.bind(dialog: Dialog) {
// Root view of the Volume Dialog.
val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
root.alpha = 0f
- root.repeatWhenAttached {
- root.viewModel(
- traceName = "VolumeDialogViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { dialogViewModelFactory.create() },
- ) { viewModel ->
- animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
-
- viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
- viewModel.motionState
- .scan(0) { acc, motionState ->
- // don't animate the initial state
- root.transitionToState(motionState, animate = acc != 0)
- acc + 1
- }
- .launchIn(this)
- launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
+ animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
- awaitCancellation()
+ viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+ viewModel.motionState
+ .scan(0) { acc, motionState ->
+ // don't animate the initial state
+ root.transitionToState(motionState, animate = acc != 0)
+ acc + 1
}
- }
- volumeDialogRingerViewBinder.bind(root)
- slidersViewBinder.bind(root)
- settingsButtonViewBinder.bind(root)
+ .launchIn(this)
+
+ launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
+
+ with(volumeDialogRingerViewBinder) { bind(root) }
+ with(slidersViewBinder) { bind(root) }
+ with(settingsButtonViewBinder) { bind(root) }
}
private fun CoroutineScope.animateVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index c7f5801a87c2..10cf615ce0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -20,6 +20,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.view.ViewPropertyAnimator
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringAnimation
import kotlin.coroutines.resume
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -80,6 +82,27 @@ suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) {
}
}
+/**
+ * Starts spring animation and suspends until it's finished. Cancels the animation if the running
+ * coroutine is cancelled.
+ */
+suspend fun SpringAnimation.suspendAnimate(
+ animationUpdateListener: DynamicAnimation.OnAnimationUpdateListener? = null,
+ animationEndListener: DynamicAnimation.OnAnimationEndListener? = null,
+) = suspendCancellableCoroutine { continuation ->
+ animationUpdateListener?.let(::addUpdateListener)
+ addEndListener { animation, canceled, value, velocity ->
+ continuation.resumeIfCan(Unit)
+ animationEndListener?.onAnimationEnd(animation, canceled, value, velocity)
+ }
+ animateToFinalPosition(1F)
+ continuation.invokeOnCancellation {
+ animationUpdateListener?.let(::removeUpdateListener)
+ animationEndListener?.let(::removeEndListener)
+ cancel()
+ }
+}
+
private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) {
if (!isCancelled && !isCompleted) {
resume(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
index e858cfef67ea..9bab1b0aa25d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -17,15 +17,18 @@
package com.android.systemui.volume.dialog.ui.viewmodel
import com.android.systemui.volume.Events
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogSafetyWarningInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
@@ -34,41 +37,45 @@ import kotlinx.coroutines.flow.mapLatest
class VolumeDialogPluginViewModel
@Inject
constructor(
- private val componentFactory: VolumeDialogComponent.Factory,
+ @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+ private val dialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor,
+ private val volumeDialogProvider: Provider<VolumeDialog>,
private val logger: VolumeDialogLogger,
) {
- suspend fun launchVolumeDialog() {
- coroutineScope {
- dialogVisibilityInteractor.dialogVisibility
- .mapLatest { visibilityModel ->
- with(visibilityModel) {
- if (this is VolumeDialogVisibilityModel.Visible) {
- showDialog(componentFactory)
- Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
- logger.onShow(reason)
- }
- if (this is VolumeDialogVisibilityModel.Dismissed) {
- Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
- logger.onDismiss(reason)
- }
+ fun launchVolumeDialog() {
+ dialogVisibilityInteractor.dialogVisibility
+ .mapLatest { visibilityModel ->
+ with(visibilityModel) {
+ if (this is VolumeDialogVisibilityModel.Visible) {
+ showDialog()
+ Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+ logger.onShow(reason)
+ }
+ if (this is VolumeDialogVisibilityModel.Dismissed) {
+ Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+ logger.onDismiss(reason)
}
}
- .launchIn(this)
- }
+ }
+ .launchIn(coroutineScope)
}
- private suspend fun showDialog(componentFactory: VolumeDialogComponent.Factory): Unit =
- coroutineScope {
- val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this)
- val dialog =
- volumeDialogComponent.volumeDialog().apply {
- setOnDismissListener {
- volumeDialogComponent.coroutineScope().cancel()
- dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
- }
+ val isShowingSafetyWarning: Flow<Boolean> = dialogSafetyWarningInteractor.isShowingSafetyWarning
+
+ fun onSafetyWarningDismissed() {
+ dialogSafetyWarningInteractor.onSafetyWarningDismissed()
+ }
+
+ private fun showDialog() {
+ volumeDialogProvider
+ .get()
+ .apply {
+ setOnDismissListener {
+ dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
}
- dialog.show()
- }
+ }
+ .show()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 0352799916bc..b20dffb8ac33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.devicePosture
import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
@@ -30,9 +31,7 @@ import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityMod
import com.android.systemui.volume.dialog.shared.model.streamLabel
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
@@ -40,14 +39,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** Provides a state for the Volume Dialog. */
-@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogScope
class VolumeDialogViewModel
-@AssistedInject
+@Inject
constructor(
private val context: Context,
- dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+ private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
- volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ private val volumeDialogStateInteractor: VolumeDialogStateInteractor,
devicePostureController: DevicePostureController,
configurationController: ConfigurationController,
) {
@@ -84,9 +83,4 @@ constructor(
val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
return isLandscape && isHalfOpen
}
-
- @AssistedFactory
- interface Factory {
- fun create(): VolumeDialogViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index e565de5c55ea..02747d7e6996 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import android.graphics.Color as GraphicsColor
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -77,7 +79,13 @@ constructor(
ConnectedDeviceViewModel(
label = label,
labelColor =
- Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ },
deviceName =
if (mediaOutputModel.isInAudioSharing) {
context.getString(R.string.audio_sharing_description)
@@ -96,11 +104,7 @@ constructor(
},
)
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
mediaOutputComponentInteractor.mediaOutputModel
@@ -121,7 +125,15 @@ constructor(
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSurface
+ )
+ }
} else {
Color.Attribute(
com.android.internal.R.attr.materialColorSurfaceContainerHighest
@@ -129,7 +141,15 @@ constructor(
},
backgroundColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSecondary
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
@@ -139,38 +159,29 @@ constructor(
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- )
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
- backgroundColor =
- if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
- } else {
- Color.Attribute(
- com.android.internal.R.attr.materialColorSurfaceContainerHighest
- )
- },
+ backgroundColor = Color.Loaded(GraphicsColor.TRANSPARENT),
)
}
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val enabled: StateFlow<Boolean> =
mediaOutputComponentInteractor.mediaOutputModel
.filterData()
.map { it.canOpenAudioSwitcher }
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- true,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, true)
fun onBarClick(expandable: Expandable?) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
@@ -178,7 +189,7 @@ constructor(
mediaOutputComponentInteractor.mediaOutputModel.value
actionsInteractor.onBarClick(
(result as? Result.Data<MediaOutputComponentModel>)?.data,
- expandable
+ expandable,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 1d32a4fd69d6..389b6fb4b0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -32,6 +32,7 @@ import android.provider.Settings;
import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
+import android.service.quickaccesswallet.WalletCard;
import android.util.Log;
import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -268,6 +269,23 @@ public class QuickAccessWalletController {
});
}
+ /**
+ * Starts the {@link android.app.PendingIntent} for a {@link WalletCard}.
+ *
+ * This should be used to open a selected card from the QuickAccessWallet UI or
+ * the settings tile.
+ *
+ * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
+ * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
+ * smooth animation for the activity launch.
+ */
+ public void startWalletCardPendingIntent(WalletCard card,
+ ActivityStarter activityStarter,
+ ActivityTransitionAnimator.Controller animationController) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ card.getPendingIntent(), animationController);
+ }
+
private Intent getSysUiWalletIntent() {
return new Intent(mContext, WalletActivity.class)
.setAction(Intent.ACTION_VIEW);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 073781e6101d..0ec71c21985e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -48,7 +48,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -60,6 +59,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.wm.shell.dagger.WMComponent;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHanded;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 2aa6e7b18154..a41725f754df 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -24,7 +24,6 @@ import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_ACTIVE
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
@@ -91,6 +90,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.clearInvocations
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -107,6 +107,7 @@ class ClockEventControllerTest : SysuiTestCase() {
private lateinit var repository: FakeKeyguardRepository
private val clockBuffers = ClockMessageBuffers(LogcatOnlyMessageBuffer(LogLevel.DEBUG))
private lateinit var underTest: ClockEventController
+ private lateinit var dndModeId: String
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@@ -156,6 +157,7 @@ class ClockEventControllerTest : SysuiTestCase() {
whenever(largeClockController.theme).thenReturn(ThemeConfig(true, null))
whenever(userTracker.userId).thenReturn(1)
+ dndModeId = MANUAL_DND_INACTIVE.id
zenModeRepository.addMode(MANUAL_DND_INACTIVE)
repository = FakeKeyguardRepository()
@@ -527,8 +529,10 @@ class ClockEventControllerTest : SysuiTestCase() {
fun listenForDnd_onDndChange_updatesClockZenMode() =
testScope.runTest {
underTest.listenForDnd(testScope.backgroundScope)
+ runCurrent()
+ clearInvocations(events)
- zenModeRepository.replaceMode(MANUAL_DND_INACTIVE.id, MANUAL_DND_ACTIVE)
+ zenModeRepository.activateMode(dndModeId)
runCurrent()
verify(events)
@@ -536,7 +540,7 @@ class ClockEventControllerTest : SysuiTestCase() {
eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
)
- zenModeRepository.replaceMode(MANUAL_DND_ACTIVE.id, MANUAL_DND_INACTIVE)
+ zenModeRepository.deactivateMode(dndModeId)
runCurrent()
verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index fa88f6207c49..ad5f96044c4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -930,6 +930,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
private void advanceTimeBy(long timeDelta) {
+ if (timeDelta == mWaitAnimationDuration) {
+ mAnimatorTestRule.advanceAnimationDuration(timeDelta);
+ return;
+ }
mAnimatorTestRule.advanceTimeBy(timeDelta);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 7c0c5c209363..4553f983b898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -59,6 +59,7 @@ import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -81,6 +82,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -544,9 +546,10 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
OnSeekBarWithIconButtonsChangeListener onChangeListener =
mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
- mZoomSeekbar.setProgress(30);
+ SeekBar mockSeekBar = Mockito.mock(SeekBar.class);
+ when(mockSeekBar.getProgress()).thenReturn(30);
onChangeListener.onUserInteractionFinalized(
- mZoomSeekbar.getSeekbar(),
+ mockSeekBar,
OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
// should trigger callback to update magnifier scale and persist the scale
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index dddaabb66022..50d0049dbcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -26,12 +26,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.graphics.PointF;
-
import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
@@ -51,7 +49,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -72,17 +69,13 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private AccessibilityManager mAccessibilityManager;
-
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- secureSettings);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
secureSettings));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 400b3b388c31..f4580c173579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -159,8 +159,7 @@ public class MenuViewLayerTest extends SysuiTestCase {
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewModel = new MenuViewModel(
- mSpyContext, mStubAccessibilityManager, mSecureSettings);
+ mMenuViewModel = new MenuViewModel(mSpyContext, mSecureSettings);
MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
mSpyContext, mStubWindowManager);
mMenuView = spy(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 7c0892891707..11199cb2f34a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -44,6 +44,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
@@ -593,11 +594,11 @@ public class AppOpsControllerTest extends SysuiTestCase {
//untrusted receiver access
mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
- TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+ TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
AppOpsManager.ATTRIBUTION_FLAG_RECEIVER, TEST_CHAIN_ID);
//untrusted intermediary access
mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
- TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+ TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY, TEST_CHAIN_ID);
assertTrue(mController.getActiveAppOps().isEmpty());
}
@@ -606,11 +607,11 @@ public class AppOpsControllerTest extends SysuiTestCase {
public void testTrustedChainUsagesKept() {
//untrusted accessor access
mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID,
- TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+ TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, TEST_CHAIN_ID);
//trusted access
mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID,
- TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true,
+ TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true,
AppOpsManager.ATTRIBUTION_FLAG_RECEIVER | AppOpsManager.ATTRIBUTION_FLAG_TRUSTED,
TEST_CHAIN_ID);
assertEquals(2, mController.getActiveAppOps().size());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 4ca84c58f6d9..50fad3bf697e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.communal.data.backup.CommunalBackupUtilsTest.FakeWid
import com.android.systemui.communal.data.db.CommunalDatabase
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.proto.toCommunalHubState
+import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
import java.io.File
@@ -120,21 +121,32 @@ class CommunalBackupHelperTest : SysuiTestCase() {
componentName = "com.android.fakePackage1/fakeWidget1",
rank = 0,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(1),
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
rank = 1,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(2),
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
rank = 2,
userSerialNumber = 10,
+ spanY = SpanValue.Responsive(3),
),
)
- .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) }
+ .onEach {
+ dao.addWidget(
+ widgetId = it.widgetId,
+ componentName = it.componentName,
+ rank = it.rank,
+ userSerialNumber = it.userSerialNumber,
+ spanY = it.spanY,
+ )
+ }
}
private fun getBackupDataInputStream(): BackupDataInputStream {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index edc8c837bf78..d31e4664d4a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -23,6 +23,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.db.CommunalDatabase
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toFixed
+import com.android.systemui.communal.shared.model.toResponsive
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
@@ -71,22 +74,25 @@ class CommunalBackupUtilsTest : SysuiTestCase() {
componentName = "com.android.fakePackage1/fakeWidget1",
rank = 0,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(1),
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
rank = 1,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(2),
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
rank = 2,
userSerialNumber = 10,
+ spanY = SpanValue.Responsive(3),
),
)
expectedWidgets.forEach {
- dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, it.spanY)
}
// Get communal hub state
@@ -150,6 +156,7 @@ class CommunalBackupUtilsTest : SysuiTestCase() {
val componentName: String,
val rank: Int,
val userSerialNumber: Int,
+ val spanY: SpanValue,
)
companion object {
@@ -163,7 +170,9 @@ class CommunalBackupUtilsTest : SysuiTestCase() {
actual?.widgetId == expected?.widgetId &&
actual?.componentName == expected?.componentName &&
actual?.rank == expected?.rank &&
- actual?.userSerialNumber == expected?.userSerialNumber
+ actual?.userSerialNumber == expected?.userSerialNumber &&
+ actual?.spanY == expected?.spanY?.toFixed()?.value &&
+ actual?.spanYNew == expected?.spanY?.toResponsive()?.value
},
"represents",
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index 7d5a334b45ea..1466e32b1296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -22,6 +22,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toResponsive
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -173,6 +175,49 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
databaseV4.verifyWidgetsV4(fakeWidgetsV3.map { it.getV4() })
}
+ @Test
+ fun migrate4To5_addNewSpanYColumn() {
+ val databaseV4 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 4)
+
+ val fakeWidgetsV4 =
+ listOf(
+ FakeCommunalWidgetItemV4(
+ widgetId = 1,
+ componentName = "test_widget_1",
+ itemId = 11,
+ userSerialNumber = 0,
+ spanY = 3,
+ ),
+ FakeCommunalWidgetItemV4(
+ widgetId = 2,
+ componentName = "test_widget_2",
+ itemId = 12,
+ userSerialNumber = 10,
+ spanY = 6,
+ ),
+ FakeCommunalWidgetItemV4(
+ widgetId = 3,
+ componentName = "test_widget_3",
+ itemId = 13,
+ userSerialNumber = 0,
+ spanY = 0,
+ ),
+ )
+ databaseV4.insertWidgetsV4(fakeWidgetsV4)
+
+ databaseV4.verifyWidgetsV4(fakeWidgetsV4)
+
+ val databaseV5 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 5,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_4_5,
+ )
+
+ databaseV5.verifyWidgetsV5(fakeWidgetsV4.map { it.getV5() })
+ }
+
private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
widgets.forEach { widget ->
execSQL(
@@ -198,6 +243,24 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
}
}
+ private fun SupportSQLiteDatabase.insertWidgetsV4(widgets: List<FakeCommunalWidgetItemV4>) {
+ widgets.forEach { widget ->
+ execSQL(
+ "INSERT INTO communal_widget_table(" +
+ "widget_id, " +
+ "component_name, " +
+ "item_id, " +
+ "user_serial_number, " +
+ "span_y) " +
+ "VALUES(${widget.widgetId}, " +
+ "'${widget.componentName}', " +
+ "${widget.itemId}, " +
+ "${widget.userSerialNumber}," +
+ "${widget.spanY})"
+ )
+ }
+ }
+
private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
val cursor = query("SELECT * FROM communal_widget_table")
assertThat(cursor.moveToFirst()).isTrue()
@@ -270,6 +333,27 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
assertThat(cursor.isAfterLast).isTrue()
}
+ private fun SupportSQLiteDatabase.verifyWidgetsV5(widgets: List<FakeCommunalWidgetItemV5>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+ assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY)
+ assertThat(cursor.getInt(cursor.getColumnIndex("span_y_new")))
+ .isEqualTo(widget.spanYNew)
+
+ cursor.moveToNext()
+ }
+
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
ranks.forEach { rank ->
execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
@@ -334,6 +418,27 @@ class CommunalDatabaseMigrationsTest : SysuiTestCase() {
val spanY: Int,
)
+ private fun FakeCommunalWidgetItemV4.getV5(): FakeCommunalWidgetItemV5 {
+ val spanYFixed = SpanValue.Fixed(spanY)
+ return FakeCommunalWidgetItemV5(
+ widgetId = widgetId,
+ componentName = componentName,
+ itemId = itemId,
+ userSerialNumber = userSerialNumber,
+ spanY = spanYFixed.value,
+ spanYNew = spanYFixed.toResponsive().value,
+ )
+ }
+
+ private data class FakeCommunalWidgetItemV5(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ val spanY: Int,
+ val spanYNew: Int,
+ )
+
private data class FakeCommunalItemRank(val rank: Int)
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 2312bbd2d7f8..2acb7750273b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -22,7 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.nano.CommunalHubState
-import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.SpanValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
@@ -68,12 +68,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
@Test
fun addWidget_readValueInDb() =
testScope.runTest {
- val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
assertThat(entry).isEqualTo(communalWidgetItemEntry1)
@@ -82,12 +83,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
@Test
fun deleteWidget_notInDb_returnsFalse() =
testScope.runTest {
- val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
}
@@ -98,12 +100,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, rank, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber, spanY) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
assertThat(widgets())
@@ -126,11 +129,12 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
// Add widgets one by one without specifying rank
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
widgetsToAdd.forEach {
- val (widgetId, provider, _, userSerialNumber) = it
+ val (widgetId, provider, _, userSerialNumber, spanY) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
@@ -153,12 +157,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, rank, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber, spanY) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
assertThat(widgets())
@@ -180,12 +185,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, rank, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber, spanY) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
assertThat(widgets())
@@ -217,12 +223,13 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val widgets = collectLastValue(communalWidgetDao.getWidgets())
existingWidgets.forEach {
- val (widgetId, provider, rank, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber, spanY) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
rank = rank,
userSerialNumber = userSerialNumber,
+ spanY = spanY,
)
}
assertThat(widgets())
@@ -242,6 +249,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_4"),
rank = 1,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(1),
)
val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
@@ -253,6 +261,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
itemId = 4L,
userSerialNumber = 0,
spanY = 3,
+ spanYNew = 1,
)
assertThat(widgets())
.containsExactly(
@@ -279,21 +288,21 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pkg_name", "cls_name_1"),
rank = 0,
userSerialNumber = 0,
- spanY = CommunalContentSize.FULL.span,
+ spanY = SpanValue.Responsive(1),
)
communalWidgetDao.addWidget(
widgetId = 2,
provider = ComponentName("pkg_name", "cls_name_2"),
rank = 1,
userSerialNumber = 0,
- spanY = CommunalContentSize.HALF.span,
+ spanY = SpanValue.Responsive(2),
)
communalWidgetDao.addWidget(
widgetId = 3,
provider = ComponentName("pkg_name", "cls_name_3"),
rank = 2,
userSerialNumber = 0,
- spanY = CommunalContentSize.THIRD.span,
+ spanY = SpanValue.Fixed(3),
)
// Verify that the widgets have the correct spanY values
@@ -306,7 +315,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = "pkg_name/cls_name_1",
itemId = 1L,
userSerialNumber = 0,
- spanY = CommunalContentSize.FULL.span,
+ spanY = 3,
+ spanYNew = 1,
),
CommunalItemRank(uid = 2L, rank = 1),
CommunalWidgetItem(
@@ -315,7 +325,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = "pkg_name/cls_name_2",
itemId = 2L,
userSerialNumber = 0,
- spanY = CommunalContentSize.HALF.span,
+ spanY = 6,
+ spanYNew = 2,
),
CommunalItemRank(uid = 3L, rank = 2),
CommunalWidgetItem(
@@ -324,7 +335,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = "pkg_name/cls_name_3",
itemId = 3L,
userSerialNumber = 0,
- spanY = CommunalContentSize.THIRD.span,
+ spanY = 3,
+ spanYNew = 1,
),
)
.inOrder()
@@ -352,7 +364,8 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = fakeWidget.componentName,
itemId = rank.uid,
userSerialNumber = fakeWidget.userSerialNumber,
- spanY = 3,
+ spanY = fakeWidget.spanY.coerceAtLeast(3),
+ spanYNew = fakeWidget.spanYNew.coerceAtLeast(1),
)
expected[rank] = widget
}
@@ -366,6 +379,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = metadata.provider,
rank = rank ?: metadata.rank,
userSerialNumber = metadata.userSerialNumber,
+ spanY = metadata.spanY,
)
}
@@ -374,6 +388,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
val provider: ComponentName,
val rank: Int,
val userSerialNumber: Int,
+ val spanY: SpanValue,
)
companion object {
@@ -383,6 +398,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_1"),
rank = 0,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(1),
)
val widgetInfo2 =
FakeWidgetMetadata(
@@ -390,6 +406,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_2"),
rank = 1,
userSerialNumber = 0,
+ spanY = SpanValue.Responsive(1),
)
val widgetInfo3 =
FakeWidgetMetadata(
@@ -397,6 +414,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_3"),
rank = 2,
userSerialNumber = 10,
+ spanY = SpanValue.Responsive(1),
)
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
@@ -409,6 +427,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
itemId = communalItemRankEntry1.uid,
userSerialNumber = widgetInfo1.userSerialNumber,
spanY = 3,
+ spanYNew = 1,
)
val communalWidgetItemEntry2 =
CommunalWidgetItem(
@@ -418,6 +437,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
itemId = communalItemRankEntry2.uid,
userSerialNumber = widgetInfo2.userSerialNumber,
spanY = 3,
+ spanYNew = 1,
)
val communalWidgetItemEntry3 =
CommunalWidgetItem(
@@ -427,6 +447,7 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
itemId = communalItemRankEntry3.uid,
userSerialNumber = widgetInfo3.userSerialNumber,
spanY = 3,
+ spanYNew = 1,
)
val fakeState =
CommunalHubState().apply {
@@ -437,12 +458,14 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = "pk_name/fake_widget_1"
rank = 1
userSerialNumber = 0
+ spanY = 3
},
CommunalHubState.CommunalWidgetItem().apply {
widgetId = 2
componentName = "pk_name/fake_widget_2"
rank = 2
userSerialNumber = 10
+ spanYNew = 1
},
)
.toTypedArray()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
new file mode 100644
index 000000000000..d7fcb6a4c2a7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -0,0 +1,471 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
+import com.android.systemui.keyboard.data.repository.keyboardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: GestureType) :
+ SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val contextualEduInteractor = kosmos.contextualEducationInteractor
+ private val repository = kosmos.contextualEducationRepository
+ private val touchpadRepository = kosmos.touchpadRepository
+ private val keyboardRepository = kosmos.keyboardRepository
+ private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository
+ private val userRepository = kosmos.fakeUserRepository
+ private val overviewProxyService = kosmos.mockOverviewProxyService
+
+ private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+ private val eduClock = kosmos.fakeEduClock
+ private val minDurationForNextEdu =
+ KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
+ private val initialDelayElapsedDuration =
+ KeyboardTouchpadEduInteractor.initialDelayDuration + 1.seconds
+
+ @Before
+ fun setup() {
+ underTest.start()
+ contextualEduInteractor.start()
+ userRepository.setUserInfos(USER_INFOS)
+ testScope.launch {
+ contextualEduInteractor.updateKeyboardFirstConnectionTime()
+ contextualEduInteractor.updateTouchpadFirstConnectionTime()
+ }
+ }
+
+ @Test
+ fun newEducationInfoOnMaxSignalCountReached() =
+ testScope.runTest {
+ triggerMaxEducationSignals(gestureType)
+ val model by collectLastValue(underTest.educationTriggered)
+
+ assertThat(model?.gestureType).isEqualTo(gestureType)
+ }
+
+ @Test
+ fun newEducationToastOn1stEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(gestureType)
+
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
+ }
+
+ @Test
+ fun newEducationNotificationOn2ndEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(gestureType)
+ // runCurrent() to trigger 1st education
+ runCurrent()
+
+ eduClock.offset(minDurationForNextEdu)
+ triggerMaxEducationSignals(gestureType)
+
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
+ }
+
+ @Test
+ fun noEducationInfoBeforeMaxSignalCountReached() =
+ testScope.runTest {
+ contextualEduInteractor.incrementSignalCount(gestureType)
+ val model by collectLastValue(underTest.educationTriggered)
+ assertThat(model).isNull()
+ }
+
+ @Test
+ fun noEducationInfoWhenShortcutTriggeredPreviously() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ contextualEduInteractor.updateShortcutTriggerTime(gestureType)
+ triggerMaxEducationSignals(gestureType)
+ assertThat(model).isNull()
+ }
+
+ @Test
+ fun no2ndEducationBeforeMinEduIntervalReached() =
+ testScope.runTest {
+ val models by collectValues(underTest.educationTriggered)
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ // Offset a duration that is less than the required education interval
+ eduClock.offset(1.seconds)
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ assertThat(models.filterNotNull().size).isEqualTo(1)
+ }
+
+ @Test
+ fun noNewEducationInfoAfterMaxEducationCountReached() =
+ testScope.runTest {
+ val models by collectValues(underTest.educationTriggered)
+ // Trigger 2 educations
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+ eduClock.offset(minDurationForNextEdu)
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ // Try triggering 3rd education
+ eduClock.offset(minDurationForNextEdu)
+ triggerMaxEducationSignals(gestureType)
+
+ assertThat(models.filterNotNull().size).isEqualTo(2)
+ }
+
+ @Test
+ fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
+ testScope.runTest {
+ val model by
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType)
+ )
+ contextualEduInteractor.incrementSignalCount(gestureType)
+ eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
+ val secondSignalReceivedTime = eduClock.instant()
+ contextualEduInteractor.incrementSignalCount(gestureType)
+
+ assertThat(model)
+ .isEqualTo(
+ GestureEduModel(
+ signalCount = 1,
+ usageSessionStartTime = secondSignalReceivedTime,
+ userId = 0,
+ gestureType = gestureType,
+ )
+ )
+ }
+
+ @Test
+ fun newTouchpadConnectionTimeOnFirstTouchpadConnected() =
+ testScope.runTest {
+ setIsAnyTouchpadConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedTouchpadConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyTouchpadConnected(true)
+ setIsAnyTouchpadConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyTouchpadConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newTouchpadConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Touchpad connected for user 0
+ setIsAnyTouchpadConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnKeyboardConnected() =
+ testScope.runTest {
+ setIsAnyKeyboardConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedKeyboardConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyKeyboardConnected(true)
+ setIsAnyKeyboardConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyKeyboardConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Keyboard connected for user 0
+ setIsAnyKeyboardConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
+ @Test
+ fun updateShortcutTimeOnKeyboardShortcutTriggered() =
+ testScope.runTest {
+ // Only All Apps needs to update the keyboard shortcut
+ assumeTrue(gestureType == ALL_APPS)
+ kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS)
+
+ val model by
+ collectLastValue(
+ kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS)
+ )
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
+ testScope.runTest {
+ assumeTrue(gestureType != ALL_APPS)
+ setUpForInitialDelayElapse()
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ touchpadRepository.setIsAnyTouchpadConnected(false)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
+ testScope.runTest {
+ assumeTrue(gestureType == ALL_APPS)
+ setUpForInitialDelayElapse()
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType)
+
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ eduClock.offset(initialDelayElapsedDuration)
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ // No offset to the clock to simulate update before initial delay
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() =
+ testScope.runTest {
+ // No update to OOBE launch time to simulate no OOBE is launched yet
+ setUpForDeviceConnection()
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
+ val originalValue = model!!.signalCount
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ private suspend fun setUpForInitialDelayElapse() {
+ tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ tutorialSchedulerRepository.updateLaunchTime(DeviceType.KEYBOARD, eduClock.instant())
+ eduClock.offset(initialDelayElapsedDuration)
+ }
+
+ fun logMetricsForToastEducation() =
+ testScope.runTest {
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ verify(kosmos.mockEduMetricsLogger)
+ .logContextualEducationTriggered(gestureType, EducationUiType.Toast)
+ }
+
+ @Test
+ fun logMetricsForNotificationEducation() =
+ testScope.runTest {
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ eduClock.offset(minDurationForNextEdu)
+ triggerMaxEducationSignals(gestureType)
+ runCurrent()
+
+ verify(kosmos.mockEduMetricsLogger)
+ .logContextualEducationTriggered(gestureType, EducationUiType.Notification)
+ }
+
+ @After
+ fun clear() {
+ testScope.launch { tutorialSchedulerRepository.clear() }
+ }
+
+ private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
+ // Increment max number of signal to try triggering education
+ for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+ contextualEduInteractor.incrementSignalCount(gestureType)
+ }
+ }
+
+ private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) {
+ touchpadRepository.setIsAnyTouchpadConnected(isConnected)
+ runCurrent()
+ }
+
+ private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) {
+ keyboardRepository.setIsAnyKeyboardConnected(isConnected)
+ runCurrent()
+ }
+
+ private fun setUpForDeviceConnection() {
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ }
+
+ private fun getOverviewProxyListener(): OverviewProxyListener {
+ val listenerCaptor = argumentCaptor<OverviewProxyListener>()
+ verify(overviewProxyService).addCallback(listenerCaptor.capture())
+ return listenerCaptor.firstValue
+ }
+
+ companion object {
+ private val USER_INFOS = listOf(UserInfo(101, "Second User", 0))
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getGestureTypes(): List<GestureType> {
+ return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 2a6d29c61890..580f631734e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,17 @@
package com.android.systemui.education.domain.interactor
-import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.contextualeducation.GestureType.HOME
import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.education.data.model.GestureEduModel
-import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
@@ -37,50 +35,42 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.touchpadRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.verify
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
+@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
-class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() {
+class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
- private val repository = kosmos.contextualEducationRepository
private val touchpadRepository = kosmos.touchpadRepository
private val keyboardRepository = kosmos.keyboardRepository
private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository
- private val userRepository = kosmos.fakeUserRepository
private val overviewProxyService = kosmos.mockOverviewProxyService
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
private val eduClock = kosmos.fakeEduClock
- private val minDurationForNextEdu =
- KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
private val initialDelayElapsedDuration =
KeyboardTouchpadEduInteractor.initialDelayDuration + 1.seconds
+ private val minIntervalForEduNotification =
+ KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
@Before
fun setup() {
underTest.start()
contextualEduInteractor.start()
- userRepository.setUserInfos(USER_INFOS)
testScope.launch {
contextualEduInteractor.updateKeyboardFirstConnectionTime()
contextualEduInteractor.updateTouchpadFirstConnectionTime()
@@ -88,312 +78,76 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) :
}
@Test
- fun newEducationInfoOnMaxSignalCountReached() =
- testScope.runTest {
- triggerMaxEducationSignals(gestureType)
- val model by collectLastValue(underTest.educationTriggered)
-
- assertThat(model?.gestureType).isEqualTo(gestureType)
- }
-
- @Test
- fun newEducationToastOn1stEducation() =
- testScope.runTest {
- val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(gestureType)
-
- assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
- }
-
- @Test
- fun newEducationNotificationOn2ndEducation() =
- testScope.runTest {
- val model by collectLastValue(underTest.educationTriggered)
- triggerMaxEducationSignals(gestureType)
- // runCurrent() to trigger 1st education
- runCurrent()
-
- eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(gestureType)
-
- assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
- }
-
- @Test
- fun noEducationInfoBeforeMaxSignalCountReached() =
- testScope.runTest {
- contextualEduInteractor.incrementSignalCount(gestureType)
- val model by collectLastValue(underTest.educationTriggered)
- assertThat(model).isNull()
- }
-
- @Test
- fun noEducationInfoWhenShortcutTriggeredPreviously() =
- testScope.runTest {
- val model by collectLastValue(underTest.educationTriggered)
- contextualEduInteractor.updateShortcutTriggerTime(gestureType)
- triggerMaxEducationSignals(gestureType)
- assertThat(model).isNull()
- }
-
- @Test
- fun no2ndEducationBeforeMinEduIntervalReached() =
- testScope.runTest {
- val models by collectValues(underTest.educationTriggered)
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- // Offset a duration that is less than the required education interval
- eduClock.offset(1.seconds)
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- assertThat(models.filterNotNull().size).isEqualTo(1)
- }
-
- @Test
- fun noNewEducationInfoAfterMaxEducationCountReached() =
- testScope.runTest {
- val models by collectValues(underTest.educationTriggered)
- // Trigger 2 educations
- triggerMaxEducationSignals(gestureType)
- runCurrent()
- eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- // Try triggering 3rd education
- eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(gestureType)
-
- assertThat(models.filterNotNull().size).isEqualTo(2)
- }
-
- @Test
- fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
- testScope.runTest {
- val model by
- collectLastValue(
- kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType)
- )
- contextualEduInteractor.incrementSignalCount(gestureType)
- eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
- val secondSignalReceivedTime = eduClock.instant()
- contextualEduInteractor.incrementSignalCount(gestureType)
-
- assertThat(model)
- .isEqualTo(
- GestureEduModel(
- signalCount = 1,
- usageSessionStartTime = secondSignalReceivedTime,
- userId = 0,
- gestureType = gestureType,
- )
- )
- }
-
- @Test
- fun newTouchpadConnectionTimeOnFirstTouchpadConnected() =
- testScope.runTest {
- setIsAnyTouchpadConnected(true)
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant())
- }
-
- @Test
- fun unchangedTouchpadConnectionTimeOnSecondConnection() =
- testScope.runTest {
- val firstConnectionTime = eduClock.instant()
- setIsAnyTouchpadConnected(true)
- setIsAnyTouchpadConnected(false)
-
- eduClock.offset(1.hours)
- setIsAnyTouchpadConnected(true)
-
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime)
- }
-
- @Test
- fun newTouchpadConnectionTimeOnUserChanged() =
- testScope.runTest {
- // Touchpad connected for user 0
- setIsAnyTouchpadConnected(true)
-
- // Change user
- eduClock.offset(1.hours)
- val newUserFirstConnectionTime = eduClock.instant()
- userRepository.setSelectedUserInfo(USER_INFOS[0])
- runCurrent()
-
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
- }
-
- @Test
- fun newKeyboardConnectionTimeOnKeyboardConnected() =
- testScope.runTest {
- setIsAnyKeyboardConnected(true)
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant())
- }
-
- @Test
- fun unchangedKeyboardConnectionTimeOnSecondConnection() =
- testScope.runTest {
- val firstConnectionTime = eduClock.instant()
- setIsAnyKeyboardConnected(true)
- setIsAnyKeyboardConnected(false)
-
- eduClock.offset(1.hours)
- setIsAnyKeyboardConnected(true)
-
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime)
- }
-
- @Test
- fun newKeyboardConnectionTimeOnUserChanged() =
- testScope.runTest {
- // Keyboard connected for user 0
- setIsAnyKeyboardConnected(true)
-
- // Change user
- eduClock.offset(1.hours)
- val newUserFirstConnectionTime = eduClock.instant()
- userRepository.setSelectedUserInfo(USER_INFOS[0])
- runCurrent()
-
- val model = contextualEduInteractor.getEduDeviceConnectionTime()
- assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
- }
-
- @Test
- fun updateShortcutTimeOnKeyboardShortcutTriggered() =
- testScope.runTest {
- // Only All Apps needs to update the keyboard shortcut
- assumeTrue(gestureType == ALL_APPS)
- kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS)
-
- val model by
- collectLastValue(
- kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS)
- )
- assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant())
- }
-
- @Test
- fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
- testScope.runTest {
- assumeTrue(gestureType != ALL_APPS)
- setUpForInitialDelayElapse()
- touchpadRepository.setIsAnyTouchpadConnected(true)
-
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
-
- assertThat(model?.signalCount).isEqualTo(originalValue + 1)
- }
-
- @Test
- fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
- testScope.runTest {
- setUpForInitialDelayElapse()
- touchpadRepository.setIsAnyTouchpadConnected(false)
-
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
-
- assertThat(model?.signalCount).isEqualTo(originalValue)
- }
-
- @Test
- fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
- testScope.runTest {
- assumeTrue(gestureType == ALL_APPS)
- setUpForInitialDelayElapse()
- keyboardRepository.setIsAnyKeyboardConnected(true)
-
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
-
- assertThat(model?.signalCount).isEqualTo(originalValue + 1)
- }
-
- @Test
- fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
+ fun newEducationToastBeforeMaxToastsPerSessionTriggered() =
testScope.runTest {
+ setUpForDeviceConnection()
setUpForInitialDelayElapse()
- keyboardRepository.setIsAnyKeyboardConnected(false)
-
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
-
- assertThat(model?.signalCount).isEqualTo(originalValue)
- }
-
- @Test
- fun dataAddedOnUpdateShortcutTriggerTime() =
- testScope.runTest {
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- assertThat(model?.lastShortcutTriggeredTime).isNull()
+ val model by collectLastValue(underTest.educationTriggered)
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType)
+ triggerEducation(HOME)
- assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
+ assertThat(model).isEqualTo(EducationInfo(HOME, EducationUiType.Toast, userId = 0))
}
@Test
- fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() =
+ fun noEducationToastAfterMaxToastsPerSessionTriggered() =
testScope.runTest {
setUpForDeviceConnection()
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ setUpForInitialDelayElapse()
+ val models by collectValues(underTest.educationTriggered.filterNotNull())
+ // Show two toasts of other gestures
+ triggerEducation(HOME)
+ triggerEducation(BACK)
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- eduClock.offset(initialDelayElapsedDuration)
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ triggerEducation(OVERVIEW)
- assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ // No new toast education besides the 2 triggered at first
+ val firstEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0)
+ val secondEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0)
+ assertThat(models).containsExactly(firstEdu, secondEdu).inOrder()
}
@Test
- fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() =
+ fun newEducationToastAfterMinIntervalElapsedWhenMaxToastsPerSessionTriggered() =
testScope.runTest {
setUpForDeviceConnection()
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ setUpForInitialDelayElapse()
+ val models by collectValues(underTest.educationTriggered.filterNotNull())
+ // Show two toasts of other gestures
+ triggerEducation(HOME)
+ triggerEducation(BACK)
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- // No offset to the clock to simulate update before initial delay
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ // Trigger toast after an usage session has elapsed
+ eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration + 1.seconds)
+ triggerEducation(OVERVIEW)
- assertThat(model?.signalCount).isEqualTo(originalValue)
+ val firstEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0)
+ val secondEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0)
+ val thirdEdu = EducationInfo(OVERVIEW, EducationUiType.Toast, userId = 0)
+ assertThat(models).containsExactly(firstEdu, secondEdu, thirdEdu).inOrder()
}
@Test
- fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() =
+ fun newEducationNotificationAfterMaxToastsPerSessionTriggered() =
testScope.runTest {
- // No update to OOBE launch time to simulate no OOBE is launched yet
setUpForDeviceConnection()
+ setUpForInitialDelayElapse()
+ val models by collectValues(underTest.educationTriggered.filterNotNull())
+ triggerEducation(BACK)
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ // Offset to let min interval for notification elapse so we could show edu notification
+ // for BACK. It would be a new usage session too because the interval (7 days) is
+ // longer than a usage session (3 days)
+ eduClock.offset(minIntervalForEduNotification)
+ triggerEducation(HOME)
+ triggerEducation(OVERVIEW)
+ triggerEducation(BACK)
- assertThat(model?.signalCount).isEqualTo(originalValue)
+ val firstEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0)
+ val secondEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0)
+ val thirdEdu = EducationInfo(OVERVIEW, EducationUiType.Toast, userId = 0)
+ val fourthEdu = EducationInfo(BACK, EducationUiType.Notification, userId = 0)
+ assertThat(models).containsExactly(firstEdu, secondEdu, thirdEdu, fourthEdu).inOrder()
}
private suspend fun setUpForInitialDelayElapse() {
@@ -402,51 +156,6 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) :
eduClock.offset(initialDelayElapsedDuration)
}
- fun logMetricsForToastEducation() =
- testScope.runTest {
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- verify(kosmos.mockEduMetricsLogger)
- .logContextualEducationTriggered(gestureType, EducationUiType.Toast)
- }
-
- @Test
- fun logMetricsForNotificationEducation() =
- testScope.runTest {
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- eduClock.offset(minDurationForNextEdu)
- triggerMaxEducationSignals(gestureType)
- runCurrent()
-
- verify(kosmos.mockEduMetricsLogger)
- .logContextualEducationTriggered(gestureType, EducationUiType.Notification)
- }
-
- @After
- fun clear() {
- testScope.launch { tutorialSchedulerRepository.clear() }
- }
-
- private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
- // Increment max number of signal to try triggering education
- for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
- contextualEduInteractor.incrementSignalCount(gestureType)
- }
- }
-
- private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) {
- touchpadRepository.setIsAnyTouchpadConnected(isConnected)
- runCurrent()
- }
-
- private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) {
- keyboardRepository.setIsAnyKeyboardConnected(isConnected)
- runCurrent()
- }
-
private fun setUpForDeviceConnection() {
touchpadRepository.setIsAnyTouchpadConnected(true)
keyboardRepository.setIsAnyKeyboardConnected(true)
@@ -458,13 +167,12 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) :
return listenerCaptor.firstValue
}
- companion object {
- private val USER_INFOS = listOf(UserInfo(101, "Second User", 0))
-
- @JvmStatic
- @Parameters(name = "{0}")
- fun getGestureTypes(): List<GestureType> {
- return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+ private fun TestScope.triggerEducation(gestureType: GestureType) {
+ // Increment max number of signal to try triggering education
+ for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+ val listener = getOverviewProxyListener()
+ listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
}
+ runCurrent()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 929b0aad3299..67e03e4bdb22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -273,6 +273,12 @@ class CustomizationProviderTest : SysuiTestCase() {
"${Contract.AUTHORITY}." +
Contract.FlagsTable.TABLE_NAME
)
+ assertThat(underTest.getType(Contract.RuntimeValuesTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}." +
+ Contract.RuntimeValuesTable.TABLE_NAME
+ )
}
@Test
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 b3cccea97e08..38acd23d282c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -137,6 +137,7 @@ import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
+import com.android.window.flags.Flags;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -157,6 +158,10 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
+
+ private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+ Flags.ensureKeyguardDoesTransitionStarting();
+
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardViewMediator mViewMediator;
@@ -277,7 +282,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mKosmos.getNotificationShadeWindowModel(),
- mKosmos::getCommunalInteractor);
+ mSecureSettings,
+ mKosmos::getCommunalInteractor,
+ mKosmos.getShadeLayoutParams());
mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
@@ -1162,6 +1169,29 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
*/
private void assertATMSLockScreenShowing(boolean showing)
throws RemoteException {
+
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+ processAllMessagesAndBgExecutorMessages();
+
+ final InOrder orderedSetLockScreenShownCalls = inOrder(mKeyguardTransitions);
+ final ArgumentCaptor<Boolean> showingCaptor = ArgumentCaptor.forClass(Boolean.class);
+ orderedSetLockScreenShownCalls
+ .verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(showingCaptor.capture(), anyBoolean());
+
+ // The captor will have the most recent startKeyguardTransition call's value.
+ assertEquals(showing, showingCaptor.getValue());
+
+ // We're now just after the last startKeyguardTransition call. If we expect the
+ // lockscreen to be showing, ensure that we didn't subsequently ask for it to go away.
+ if (showing) {
+ orderedSetLockScreenShownCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(false), anyBoolean());
+ }
+ return;
+ }
+
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
@@ -1190,6 +1220,20 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ final InOrder orderedGoingAwayCalls = inOrder(mKeyguardTransitions);
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(eq(false) /* keyguardShowing */,
+ eq(false) /* aodShowing */);
+
+ // Advance the inOrder to just past the last goingAway call. Let's make sure we didn't
+ // re-show the lockscreen, which would cancel going away.
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(true) /* keyguardShowing */,
+ anyBoolean() /* aodShowing */);
+ return;
+ }
+
final InOrder orderedGoingAwayCalls = inOrder(mActivityTaskManagerService);
orderedGoingAwayCalls.verify(mActivityTaskManagerService, atLeastOnce())
.keyguardGoingAway(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index eb1b44b75247..7ba797c03a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1467,4 +1467,66 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
verify(mInputRouteManager, never()).selectDevice(outputMediaDevice);
verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice);
}
+
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs() {
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ // Mock the selected output device.
+ doReturn(Collections.singletonList(mMediaDevice1))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ // Verify that there is initially one "Connect a device" button present.
+ assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+
+ // Change the selected device, and verify that there is still one "Connect a device" button
+ // present.
+ doReturn(Collections.singletonList(mMediaDevice2))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs_inputRoutingEnabled() {
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ // Mock the selected output device.
+ doReturn(Collections.singletonList(mMediaDevice1))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ // Mock the selected input media device.
+ final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class);
+ doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
+
+ // Verify that there is initially one "Connect a device" button present.
+ assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+
+ // Change the selected device, and verify that there is still one "Connect a device" button
+ // present.
+ doReturn(Collections.singletonList(mMediaDevice2))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ }
+
+ private int getNumberOfConnectDeviceButtons() {
+ int numberOfConnectDeviceButtons = 0;
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE) {
+ numberOfConnectDeviceButtons++;
+ }
+ }
+ return numberOfConnectDeviceButtons;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index bc3c0d96fa22..d59a404b15bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -49,6 +49,7 @@ import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.views.NavigationBar;
import com.android.systemui.recents.OverviewProxyService;
@@ -56,7 +57,7 @@ import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -89,6 +90,11 @@ public class NavigationBarControllerImplTest extends SysuiTestCase {
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
+ private final AutoHideControllerStore mAutoHideControllerStore =
+ mKosmos.getAutoHideControllerStore();
+
@Mock
private CommandQueue mCommandQueue;
@Mock
@@ -113,7 +119,7 @@ public class NavigationBarControllerImplTest extends SysuiTestCase {
mTaskbarDelegate,
mNavigationBarFactory,
mock(DumpManager.class),
- mock(AutoHideController.class),
+ mAutoHideControllerStore,
mock(LightBarController.class),
TaskStackChangeListeners.getTestInstance(),
Optional.of(mock(Pip.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index d88b75896a58..266cb51cc855 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -181,11 +181,9 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
@Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
- fun handlesShortcut_metaCtrlN() {
+ fun handlesShortcut_keyGestureTypeOpenNotes() {
val gestureEvent =
KeyGestureEvent.Builder()
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
- .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
.setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 2db5e83cf185..d058484de204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -28,13 +28,14 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.res.R
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 java.util.Arrays
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -45,7 +46,6 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.Arrays
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -62,16 +62,13 @@ class TileRequestDialogTest : SysuiTestCase() {
private lateinit var dialog: TileRequestDialog
- @Mock
- private lateinit var ugm: IUriGrantsManager
+ @Mock private lateinit var ugm: IUriGrantsManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
// Create in looper so we can make sure that the tile is fully updated
- TestableLooper.get(this).runWithLooper {
- dialog = TileRequestDialog(mContext)
- }
+ TestableLooper.get(this).runWithLooper { dialog = TileRequestDialog(mContext) }
}
@After
@@ -84,7 +81,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectViews() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -99,7 +96,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectAppName() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -112,7 +109,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectLabel() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -127,7 +124,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasIcon() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -141,7 +138,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_nullIcon_hasIcon() {
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, null, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -156,7 +153,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasNoStateDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -172,7 +169,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileNotClickable() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -189,7 +186,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileHasCorrectContentDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -206,20 +203,14 @@ class TileRequestDialogTest : SysuiTestCase() {
fun uriIconLoadSuccess_correctIcon() {
val tintColor = Color.BLACK
val icon = Mockito.mock(Icon::class.java)
- val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
- setTint(tintColor)
- }
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
whenever(icon.loadDrawable(any())).thenReturn(drawable)
- whenever(icon.loadDrawableCheckingUriGrant(
- any(),
- eq(ugm),
- anyInt(),
- anyString())
- ).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+ .thenReturn(drawable)
val size = 100
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -231,9 +222,7 @@ class TileRequestDialogTest : SysuiTestCase() {
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
val tile = content.getChildAt(1) as QSTileView
- val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
- setTint(tintColor)
- }
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
}
@@ -242,20 +231,14 @@ class TileRequestDialogTest : SysuiTestCase() {
fun uriIconLoadFail_defaultIcon() {
val tintColor = Color.BLACK
val icon = Mockito.mock(Icon::class.java)
- val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
- setTint(tintColor)
- }
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
whenever(icon.loadDrawable(any())).thenReturn(drawable)
- whenever(icon.loadDrawableCheckingUriGrant(
- any(),
- eq(ugm),
- anyInt(),
- anyString())
- ).thenReturn(null)
+ whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+ .thenReturn(null)
val size = 100
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -267,13 +250,9 @@ class TileRequestDialogTest : SysuiTestCase() {
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
val tile = content.getChildAt(1) as QSTileView
- val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
- setTint(tintColor)
- }
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
- val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
- setTint(tintColor)
- }
+ val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply { setTint(tintColor) }
assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
}
@@ -308,4 +287,3 @@ private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
b.getPixels(bPix, 0, w, 0, 0, w, h)
return Arrays.equals(aPix, bPix)
}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
index 89ec687ad123..82e247714794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.content.DialogInterface
import android.graphics.drawable.Icon
import android.os.RemoteException
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -29,8 +30,12 @@ import com.android.internal.statusbar.IAddTileResultCallback
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -52,7 +57,8 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
-class TileServiceRequestControllerTest : SysuiTestCase() {
+@DisableFlags(value = [QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME])
+class TileServiceRequestControllerTestComposeOff : SysuiTestCase() {
companion object {
private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
@@ -61,20 +67,15 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
private const val TEST_UID = 12345
}
- @Mock
- private lateinit var tileRequestDialog: TileRequestDialog
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var commandRegistry: CommandRegistry
- @Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
- private lateinit var logger: TileRequestDialogEventLogger
- @Mock
- private lateinit var icon: Icon
- @Mock
- private lateinit var ugm: IUriGrantsManager
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var tileRequestDialog: TileRequestDialog
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var commandRegistry: CommandRegistry
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var logger: TileRequestDialogEventLogger
+ @Mock private lateinit var icon: Icon
+ @Mock private lateinit var ugm: IUriGrantsManager
private val instanceIdSequence = InstanceIdSequenceFake(1_000)
private lateinit var controller: TileServiceRequestController
@@ -88,15 +89,17 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
// Tile not present by default
`when`(qsHost.indexOf(anyString())).thenReturn(-1)
- controller = TileServiceRequestController(
+ controller =
+ TileServiceRequestController(
qsHost,
commandQueue,
commandRegistry,
logger,
ugm,
- ) {
- tileRequestDialog
- }
+ kosmos.tileRequestDialogComposeDelegateFactory,
+ ) {
+ tileRequestDialog
+ }
controller.init()
}
@@ -104,24 +107,19 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun requestTileAdd_dataIsPassedToDialog() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
- verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(
- TEST_UID,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- TEST_COMPONENT.packageName,
- ),
+ verify(tileRequestDialog)
+ .setTileData(
+ TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
ugm,
- )
+ )
}
@Test
@@ -130,12 +128,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
@@ -156,12 +154,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun showAllUsers_set() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
verify(tileRequestDialog).setShowForAllUsers(true)
}
@@ -169,12 +167,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun cancelOnTouchOutside_set() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
verify(tileRequestDialog).setCanceledOnTouchOutside(true)
}
@@ -189,16 +187,16 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun cancelListener_dismissResult() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -210,7 +208,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun dialogCancelled_logged() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -219,26 +217,27 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
cancelListenerCaptor.value.onCancel(tileRequestDialog)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
fun positiveActionListener_tileAddedResult() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
@@ -251,7 +250,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun tileAdded_logged() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -260,26 +259,27 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
fun negativeActionListener_tileNotAddedResult() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
@@ -292,7 +292,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun tileNotAdded_logged() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -301,11 +301,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
@@ -319,24 +320,19 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
captor.value.requestAddTile(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
- verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(
- TEST_UID,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- TEST_COMPONENT.packageName,
- ),
+ verify(tileRequestDialog)
+ .setTileData(
+ TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
ugm,
- )
+ )
}
@Test
@@ -354,22 +350,23 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun interfaceThrowsRemoteException_doesntCrash() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
- val callback = object : IAddTileResultCallback.Stub() {
- override fun onTileRequest(p0: Int) {
- throw RemoteException()
+ val callback =
+ object : IAddTileResultCallback.Stub() {
+ override fun onTileRequest(p0: Int) {
+ throw RemoteException()
+ }
}
- }
captor.value.requestAddTile(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -383,12 +380,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -407,12 +404,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -435,12 +432,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index d88d69da5e59..d2317e4f533d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -22,7 +22,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
@@ -182,11 +185,14 @@ class DragAndDropTest : SysuiTestCase() {
}
private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) {
- onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply {
- fetchSemanticsNodes().forEachIndexed { index, _ ->
- get(index).assert(hasContentDescription(specs[index]))
+ onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG)
+ .onChildren()
+ .filter(SemanticsMatcher.keyIsDefined(SemanticsProperties.ContentDescription))
+ .apply {
+ fetchSemanticsNodes().forEachIndexed { index, _ ->
+ get(index).assert(hasContentDescription(specs[index]))
+ }
}
- }
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 9a924ed5a630..d090c01a39d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -22,8 +22,10 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
@@ -81,7 +83,7 @@ class BluetoothTileTest : SysuiTestCase() {
qsLogger,
bluetoothController,
featureFlags,
- bluetoothTileDialogViewModel
+ bluetoothTileDialogViewModel,
)
tile.initialize()
@@ -109,8 +111,7 @@ class BluetoothTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off))
}
@Test
@@ -121,8 +122,7 @@ class BluetoothTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off))
}
@Test
@@ -133,8 +133,7 @@ class BluetoothTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_on))
}
@Test
@@ -145,8 +144,7 @@ class BluetoothTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_search))
}
@Test
@@ -161,11 +159,10 @@ class BluetoothTileTest : SysuiTestCase() {
.isEqualTo(
mContext.getString(
R.string.quick_settings_bluetooth_secondary_label_battery_level,
- Utils.formatPercentage(50)
+ Utils.formatPercentage(50),
)
)
- verify(bluetoothController)
- .addOnMetadataChangedListener(eq(cachedDevice), any(), any())
+ verify(bluetoothController).addOnMetadataChangedListener(eq(cachedDevice), any(), any())
}
@Test
@@ -186,7 +183,7 @@ class BluetoothTileTest : SysuiTestCase() {
.isEqualTo(
mContext.getString(
R.string.quick_settings_bluetooth_secondary_label_battery_level,
- Utils.formatPercentage(25)
+ Utils.formatPercentage(25),
)
)
verify(bluetoothController, times(1))
@@ -197,7 +194,7 @@ class BluetoothTileTest : SysuiTestCase() {
fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
`when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
- .thenReturn(false)
+ .thenReturn(false)
`when`(clickJob.isCompleted).thenReturn(false)
tile.mClickJob = clickJob
@@ -210,7 +207,7 @@ class BluetoothTileTest : SysuiTestCase() {
fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
`when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
- .thenReturn(false)
+ .thenReturn(false)
tile.handleClick(null)
@@ -221,7 +218,7 @@ class BluetoothTileTest : SysuiTestCase() {
fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
`when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
- .thenReturn(true)
+ .thenReturn(true)
tile.handleClick(null)
@@ -265,7 +262,7 @@ class BluetoothTileTest : SysuiTestCase() {
qsLogger: QSLogger,
bluetoothController: BluetoothController,
featureFlags: FeatureFlagsClassic,
- bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
+ bluetoothTileDialogViewModel: BluetoothTileDialogViewModel,
) :
BluetoothTile(
qsHost,
@@ -279,13 +276,13 @@ class BluetoothTileTest : SysuiTestCase() {
qsLogger,
bluetoothController,
featureFlags,
- bluetoothTileDialogViewModel
+ bluetoothTileDialogViewModel,
) {
var restrictionChecked: String? = null
override fun checkIfRestrictionEnforcedByAdminOnly(
state: QSTile.State?,
- userRestriction: String?
+ userRestriction: String?,
) {
restrictionChecked = userRestriction
}
@@ -321,7 +318,7 @@ class BluetoothTileTest : SysuiTestCase() {
fun listenToDeviceMetadata(
state: QSTile.BooleanState,
cachedDevice: CachedBluetoothDevice,
- batteryLevel: Int
+ batteryLevel: Int,
) {
val btDevice = mock<BluetoothDevice>()
whenever(cachedDevice.device).thenReturn(btDevice)
@@ -332,4 +329,12 @@ class BluetoothTileTest : SysuiTestCase() {
addConnectedDevice(cachedDevice)
tile.handleUpdateState(state, /* arg= */ null)
}
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 6a43a61dad77..56b76314a3a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -29,7 +29,6 @@ import android.view.ContextThemeWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -39,14 +38,18 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsInCompose.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import java.io.File
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -55,9 +58,8 @@ import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.io.File
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -70,41 +72,29 @@ class DndTileTest : SysuiTestCase() {
private const val KEY = Settings.Secure.ZEN_DURATION
}
- @Mock
- private lateinit var qsHost: QSHost
+ @Mock private lateinit var qsHost: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var uiEventLogger: QsEventLogger
- @Mock
- private lateinit var zenModeController: ZenModeController
+ @Mock private lateinit var zenModeController: ZenModeController
- @Mock
- private lateinit var sharedPreferences: SharedPreferences
+ @Mock private lateinit var sharedPreferences: SharedPreferences
- @Mock
- private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
+ @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
- @Mock
- private lateinit var hostDialog: Dialog
+ @Mock private lateinit var hostDialog: Dialog
- @Mock
- private lateinit var expandable: Expandable
+ @Mock private lateinit var expandable: Expandable
- @Mock
- private lateinit var controller: DialogTransitionAnimator.Controller
+ @Mock private lateinit var controller: DialogTransitionAnimator.Controller
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
@@ -118,31 +108,32 @@ class DndTileTest : SysuiTestCase() {
whenever(qsHost.userId).thenReturn(DEFAULT_USER)
- val wrappedContext = object : ContextWrapper(
- ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
- ) {
- override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
- return sharedPreferences
+ val wrappedContext =
+ object :
+ ContextWrapper(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)) {
+ override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
+ return sharedPreferences
+ }
}
- }
whenever(qsHost.context).thenReturn(wrappedContext)
whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
- tile = DndTile(
- qsHost,
- uiEventLogger,
- testableLooper.looper,
- Handler(testableLooper.looper),
- FalsingManagerFake(),
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- zenModeController,
- sharedPreferences,
- secureSettings,
- mDialogTransitionAnimator
- )
+ tile =
+ DndTile(
+ qsHost,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ zenModeController,
+ sharedPreferences,
+ secureSettings,
+ mDialogTransitionAnimator,
+ )
}
@After
@@ -222,7 +213,7 @@ class DndTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_dnd_icon_off))
}
@Test
@@ -232,6 +223,14 @@ class DndTileTest : SysuiTestCase() {
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_dnd_icon_on))
+ }
+
+ private fun createExpectedIcon(resId: Int): QSTile.Icon {
+ return if (isEnabled) {
+ DrawableIconWithRes(mContext.getDrawable(resId), resId)
+ } else {
+ QSTileImpl.ResourceIcon.get(resId)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 190d80f9f6c4..f043f63885be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -45,9 +45,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -246,13 +248,13 @@ public class DreamTileTest extends SysuiTestCase {
dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_DESK);
receiver.onReceive(mContext, dockIntent);
mTestableLooper.processAllMessages();
- assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver),
+ assertEquals(createExpectedIcon(R.drawable.ic_qs_screen_saver),
dockedTile.getState().icon);
dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
receiver.onReceive(mContext, dockIntent);
mTestableLooper.processAllMessages();
- assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked),
+ assertEquals(createExpectedIcon(R.drawable.ic_qs_screen_saver_undocked),
dockedTile.getState().icon);
destroyTile(dockedTile);
@@ -268,6 +270,14 @@ public class DreamTileTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
}
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
+ }
+
private DreamTile constructTileForTest(boolean dreamSupported,
boolean dreamOnlyEnabledForSystemUser) {
return new DreamTile(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 5bd6944e863f..2b4cf5dbc225 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
@@ -144,7 +145,7 @@ public class HotspotTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_off));
}
@Test
@@ -156,7 +157,7 @@ public class HotspotTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_search));
}
@Test
@@ -168,6 +169,14 @@ public class HotspotTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, /* arg= */ null);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_on));
+ }
+
+ private QSTile.Icon createExpectedIcon(int resId) {
+ if (QsInCompose.isEnabled()) {
+ return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
+ } else {
+ return QSTileImpl.ResourceIcon.get(resId);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index afff4858499a..a17f100904be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -36,6 +36,7 @@ import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.media.projection.StopReason;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -154,7 +155,7 @@ public class RecordingControllerTest extends SysuiTestCase {
PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
mController.startCountdown(0, 0, startIntent, stopIntent);
- mController.stopRecording();
+ mController.stopRecording(StopReason.STOP_UNKNOWN);
assertFalse(mController.isStarting());
assertFalse(mController.isRecording());
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 f7059e244084..a64ff321cd4d 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
@@ -31,6 +31,7 @@ import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
@@ -70,8 +71,8 @@ class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() {
mSetFlagsRule.setFlagsParameterization(flags)
}
- val viewId by lazy {
- if (QSComposeFragment.isEnabled) {
+ private val viewId by lazy {
+ if (QsInCompose.isEnabled) {
R.id.brightness_dialog_slider
} else {
R.id.brightness_mirror_container
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e7fb470cfa76..c410111bc2e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,7 +33,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -43,7 +43,9 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -131,20 +133,26 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
underTest =
GlanceableHubContainerController(
communalInteractor,
+ communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
- kosmos.keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
shadeInteractor,
powerManager,
communalColors,
ambientTouchComponentFactory,
communalContent,
- kosmos.sceneDataSourceDelegator,
- kosmos.notificationStackScrollLayoutController,
- kosmos.keyguardMediaController,
- kosmos.lockscreenSmartspaceController,
+ sceneDataSourceDelegator,
+ notificationStackScrollLayoutController,
+ keyguardMediaController,
+ lockscreenSmartspaceController,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
)
+
+ // Make below last notification true by default or else touches will be ignored by
+ // default when the hub is not showing.
+ whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+ .thenReturn(true)
}
testableLooper = TestableLooper.get(this)
@@ -178,6 +186,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -207,6 +216,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
val underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -231,6 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
val underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -631,7 +642,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
@@ -688,7 +699,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
@@ -721,11 +732,13 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
- @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
with(kosmos) {
testScope.runTest {
+ kosmos.setCommunalV2ConfigEnabled(true)
+
// On lockscreen.
goToScene(CommunalScenes.Blank)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index 382b307fb9de..2371ccc8b9ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -1,11 +1,9 @@
package com.android.systemui.statusbar.notification
-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.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
@@ -148,7 +146,6 @@ class RoundableTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radius_maxed_to_height() {
whenever(targetView.height).thenReturn(10)
roundable.requestRoundness(1f, 1f, SOURCE1)
@@ -157,7 +154,6 @@ class RoundableTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_topRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
roundable.requestRoundness(1f, 0f, SOURCE1)
@@ -166,7 +162,6 @@ class RoundableTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_bottomRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
roundable.requestRoundness(0f, 1f, SOURCE1)
@@ -175,7 +170,6 @@ class RoundableTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radii_kept() {
whenever(targetView.height).thenReturn(100)
roundable.requestRoundness(1f, 1f, SOURCE1)
@@ -188,16 +182,9 @@ class RoundableTest : SysuiTestCase() {
assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
}
- class FakeRoundable(
- targetView: View,
- radius: Float = MAX_RADIUS,
- ) : Roundable {
+ class FakeRoundable(targetView: View, radius: Float = MAX_RADIUS) : Roundable {
override val roundableState =
- RoundableState(
- targetView = targetView,
- roundable = this,
- maxRadius = radius,
- )
+ RoundableState(targetView = targetView, roundable = this, maxRadius = radius)
override val clipHeight: Int
get() = roundableState.targetView.height
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 69efa87a9cac..abdd79761ce0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,6 +33,7 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.flags.FeatureFlags
@@ -44,6 +45,7 @@ import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.StatusBarLongPressGestureDetector
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
@@ -60,12 +62,14 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
import java.util.Optional
import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -98,6 +102,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
+ @Mock private lateinit var statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -329,6 +334,30 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onTouch_actionDown_propagatesToDisplayPolicy() {
+ controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+
+ verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId))
+ }
+
+ @Test
+ @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
+ controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0))
+
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ }
+
+ @Test
+ @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
+ controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ }
+
+ @Test
fun shadeIsExpandedOnStatusIconMouseClick() {
val view = createViewMock()
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -402,6 +431,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
mStatusOverlayHoverListenerFactory,
fakeDarkIconDispatcher,
statusBarContentInsetsProviderStore,
+ Lazy { statusBarTouchShadeDisplayPolicy },
)
.create(view)
.also { it.init() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index d1e4f646a382..3e24fbe6cd8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization;
import com.android.systemui.statusbar.phone.ui.DarkIconManager;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder;
@@ -155,9 +156,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
any(StatusBarWindowStateListener.class));
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testDisableNone() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testDisableNone() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -166,9 +167,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -184,9 +185,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -214,9 +215,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -231,9 +232,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -247,9 +248,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -271,9 +272,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testDisableNotifications() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testDisableNotifications() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
@@ -307,9 +308,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testDisableClock() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testDisableClock() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
@@ -343,10 +344,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_shadeOpenAndShouldHide_everythingHidden() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_shadeOpenAndShouldHide_everythingHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the shade is open and configured to hide the status bar icons
@@ -361,10 +362,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_shadeOpenButNotShouldHide_everythingShown() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_shadeOpenButNotShouldHide_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the shade is open but *not* configured to hide the status bar icons
@@ -379,11 +380,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- /** Regression test for b/279790651. */
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
+ /** Regression test for b/279790651. */
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the shade is open and configured to hide the status bar icons
@@ -409,9 +410,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_notTransitioningToOccluded_everythingShown() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_notTransitioningToOccluded_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(false);
@@ -424,10 +425,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_isTransitioningToOccluded_everythingHidden() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_isTransitioningToOccluded_everythingHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(true);
@@ -440,10 +441,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the transition is occurring
@@ -472,9 +473,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getUserChipView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_noOngoingCall_chipHidden() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_noOngoingCall_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
@@ -484,9 +489,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -497,9 +506,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -510,9 +523,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_hasOngoingCallButAlsoHun_chipHidden() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_hasOngoingCallButAlsoHun_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -523,9 +540,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_ongoingCallEnded_chipHidden() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_ongoingCallEnded_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Ongoing call started
@@ -547,9 +568,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
fragment.enableAnimationsForTesting();
@@ -564,9 +589,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
- public void screenSharingChipsDisabled_ignoresNewCallback() {
+ @Test
+ @DisableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void screenSharingChipsDisabled_ignoresNewCallback() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN there *is* an ongoing call via old callback
@@ -597,10 +626,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void noOngoingActivity_chipHidden() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void noOngoingActivity_chipHidden() {
resumeAndGetFragment();
// TODO(b/332662551): We *should* be able to just set a value on
@@ -615,10 +644,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -630,12 +659,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @EnableFlags({
- FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
- StatusBarNotifChips.FLAG_NAME,
- StatusBarRootModernization.FLAG_NAME})
- public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
+ @Test
+ @EnableFlags({
+ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
resumeAndGetFragment();
assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
@@ -658,10 +689,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -672,10 +707,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -687,10 +722,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -704,10 +743,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -722,10 +761,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -739,10 +782,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -757,10 +800,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
resumeAndGetFragment();
// Ongoing activity started
@@ -780,10 +827,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
resumeAndGetFragment();
// Ongoing activity started
@@ -803,10 +850,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void secondaryOngoingActivityEnded_chipHidden() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void secondaryOngoingActivityEnded_chipHidden() {
resumeAndGetFragment();
// Secondary ongoing activity started
@@ -826,10 +873,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
fragment.enableAnimationsForTesting();
@@ -845,10 +896,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
fragment.enableAnimationsForTesting();
@@ -864,10 +915,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
- @Test
- @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
- public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags({
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME
+ })
+ public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN there *is* an ongoing call via old callback
@@ -897,10 +952,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
+ @Test
+ @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN there *is* an ongoing call via old callback
@@ -931,10 +986,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getSecondaryOngoingActivityChipView().getVisibility());
}
- @Test
- @EnableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
+ @Test
+ @EnableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false);
@@ -945,10 +1000,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @EnableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void isHomeStatusBarAllowedByScene_true_everythingShown() {
+ @Test
+ @EnableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void isHomeStatusBarAllowedByScene_true_everythingShown() {
resumeAndGetFragment();
mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(true);
@@ -959,10 +1014,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @EnableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
+ @Test
+ @EnableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the scene doesn't allow the status bar
@@ -977,10 +1032,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @EnableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
+ @Test
+ @EnableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN the scene does allow the status bar
@@ -995,10 +1050,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
resumeAndGetFragment();
// Even if the scene says to hide the home status bar
@@ -1010,9 +1065,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_isDozing_clockAndSystemInfoVisible() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -1022,9 +1077,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_NotDozing_clockAndSystemInfoVisible() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -1034,9 +1089,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
@@ -1045,9 +1100,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.GONE, getClockView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
@@ -1098,10 +1153,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertFalse(contains);
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
mockSecureCameraLaunch(fragment, true /* launched */);
@@ -1121,10 +1176,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableSceneContainer
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
+ @Test
+ @DisableSceneContainer
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN a transition to dream has started
@@ -1158,9 +1213,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
- @Test
- @DisableFlags(StatusBarRootModernization.FLAG_NAME)
- public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
+ @Test
+ @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+ public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// WHEN a transition to dream has started but we're *not* dreaming
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 763449028f28..728f4183ccce 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
@@ -655,14 +655,14 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false, false, false)
)
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true, false, false)
)
assertThat(latest).isTrue()
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 af14edd10f5f..192d66c44aa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -113,7 +113,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -141,6 +141,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -154,7 +155,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -162,6 +162,7 @@ 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;
@@ -367,7 +368,7 @@ public class BubblesTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
private UserHandle mUser0;
@@ -442,7 +443,9 @@ public class BubblesTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mNotificationShadeWindowModel,
- mKosmos::getCommunalInteractor
+ new FakeSettings(),
+ mKosmos::getCommunalInteractor,
+ mKosmos.getShadeLayoutParams()
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
index 56867640d03d..74330c13f47e 100644
--- a/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
@@ -18,5 +18,8 @@ package android.content.res
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
var Kosmos.mainResources: Resources by Kosmos.Fixture { applicationContext.resources }
+
+var Kosmos.mockResources: Resources by Kosmos.Fixture { mock(Resources::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index cc0597bc3853..76fc61185b49 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -30,6 +30,7 @@ import android.os.Bundle
import android.os.IBinder
import android.os.UserHandle
import android.util.ArrayMap
+import android.view.Display
import android.view.KeyEvent
import com.android.internal.logging.InstanceId
import com.android.internal.statusbar.IAddTileResultCallback
@@ -95,6 +96,90 @@ class FakeStatusBarService : IStatusBarService.Stub() {
)
)
+ var statusBarIconsSecondaryDisplay =
+ ArrayMap<String, StatusBarIcon>().also {
+ it["slot1"] = mock<StatusBarIcon>()
+ it["slot2"] = mock<StatusBarIcon>()
+ }
+ var disabledFlags1SecondaryDisplay = 12345678
+ var appearanceSecondaryDisplay = 1234
+ var appearanceRegionsSecondaryDisplay =
+ arrayOf(
+ AppearanceRegion(
+ /* appearance = */ 123,
+ /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+ ),
+ AppearanceRegion(
+ /* appearance = */ 345,
+ /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+ ),
+ )
+ var imeWindowVisSecondaryDisplay = 9876
+ var imeBackDispositionSecondaryDisplay = 654
+ var showImeSwitcherSecondaryDisplay = true
+ var disabledFlags2SecondaryDisplay = 87654321
+ var navbarColorManagedByImeSecondaryDisplay = true
+ var behaviorSecondaryDisplay = 234
+ var requestedVisibleTypesSecondaryDisplay = 345
+ var packageNameSecondaryDisplay = "fake.bar.ser.vice"
+ var transientBarTypesSecondaryDisplay = 0
+ var letterboxDetailsSecondaryDisplay =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds = */ Rect(
+ /* left= */ 5,
+ /* top= */ 6,
+ /* right= */ 7,
+ /* bottom= */ 8,
+ ),
+ /* letterboxFullBounds = */ Rect(
+ /* left= */ 1,
+ /* top= */ 2,
+ /* right= */ 3,
+ /* bottom= */ 4,
+ ),
+ /* appAppearance = */ 123,
+ )
+ )
+
+ private val defaultRegisterStatusBarResult
+ get() =
+ RegisterStatusBarResult(
+ statusBarIcons,
+ disabledFlags1,
+ appearance,
+ appearanceRegions,
+ imeWindowVis,
+ imeBackDisposition,
+ showImeSwitcher,
+ disabledFlags2,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibleTypes,
+ packageName,
+ transientBarTypes,
+ letterboxDetails,
+ )
+
+ private val registerStatusBarResultSecondaryDisplay
+ get() =
+ RegisterStatusBarResult(
+ statusBarIconsSecondaryDisplay,
+ disabledFlags1SecondaryDisplay,
+ appearanceSecondaryDisplay,
+ appearanceRegionsSecondaryDisplay,
+ imeWindowVisSecondaryDisplay,
+ imeBackDispositionSecondaryDisplay,
+ showImeSwitcherSecondaryDisplay,
+ disabledFlags2SecondaryDisplay,
+ navbarColorManagedByImeSecondaryDisplay,
+ behaviorSecondaryDisplay,
+ requestedVisibleTypesSecondaryDisplay,
+ packageNameSecondaryDisplay,
+ transientBarTypesSecondaryDisplay,
+ letterboxDetailsSecondaryDisplay,
+ )
+
override fun expandNotificationsPanel() {}
override fun collapsePanels() {}
@@ -140,21 +225,16 @@ class FakeStatusBarService : IStatusBarService.Stub() {
override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
registeredStatusBar = callbacks
- return RegisterStatusBarResult(
- statusBarIcons,
- disabledFlags1,
- appearance,
- appearanceRegions,
- imeWindowVis,
- imeBackDisposition,
- showImeSwitcher,
- disabledFlags2,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- transientBarTypes,
- letterboxDetails,
+ return defaultRegisterStatusBarResult
+ }
+
+ override fun registerStatusBarForAllDisplays(
+ callbacks: IStatusBar
+ ): Map<String, RegisterStatusBarResult> {
+ registeredStatusBar = callbacks
+ return mapOf(
+ DEFAULT_DISPLAY_ID.toString() to defaultRegisterStatusBarResult,
+ SECONDARY_DISPLAY_ID.toString() to registerStatusBarResultSecondaryDisplay,
)
}
@@ -352,4 +432,9 @@ class FakeStatusBarService : IStatusBarService.Stub() {
override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
override fun showRearDisplayDialog(currentBaseState: Int) {}
+
+ companion object {
+ const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ const val SECONDARY_DISPLAY_ID = 2
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
new file mode 100644
index 000000000000..003777aca687
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.app
+
+import android.app.IUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.iUriGrantsManager by Kosmos.Fixture { mock<IUriGrantsManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 153a8be06adc..3e44364dc6a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -118,6 +118,7 @@ public abstract class SysuiTestCase {
android.net.platform.flags.Flags.class,
android.os.Flags.class,
android.service.controls.flags.Flags.class,
+ android.service.quickaccesswallet.Flags.class,
com.android.internal.telephony.flags.Flags.class,
com.android.server.notification.Flags.class,
com.android.systemui.Flags.class);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index e2fc44fd2d0d..eb0aee415fdd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.animation
+import android.animation.Animator
import java.util.function.Consumer
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@@ -71,6 +72,22 @@ class AnimatorTestRule(test: Any?) : TestRule {
}
/**
+ * This is similar to [advanceTimeBy] but it expects to reach the end of an animation. This call
+ * may produce 2 frames for the last animation frame and end animation callback.
+ *
+ * @param durationMs the duration that is greater than or equal to the animation duration.
+ */
+ fun advanceAnimationDuration(durationMs: Long) {
+ advanceTimeBy(durationMs)
+ if (Animator.isPostNotifyEndListenerEnabled()) {
+ // If the post-end-callback is enabled, the AnimatorListener#onAnimationEnd will be
+ // called on the next frame of last animation frame. So trigger additional doFrame to
+ // ensure the end callback method is called (by android.animation.AnimatorTestRule).
+ advanceTimeBy(0)
+ }
+ }
+
+ /**
* Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is
* a different time than the time tracked by {@link SystemClock}.
*/
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
new file mode 100644
index 000000000000..470a8e4b6ef2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.biometrics
+
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fingerprintManager by Kosmos.Fixture { mock<FingerprintManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
index ae592b968f8b..646c19086a59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -17,20 +17,19 @@
package com.android.systemui.biometrics.domain.interactor
import android.content.applicationContext
-import android.hardware.fingerprint.FingerprintManager
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
val Kosmos.udfpsOverlayInteractor by Fixture {
UdfpsOverlayInteractor(
context = applicationContext,
authController = authController,
selectedUserInteractor = selectedUserInteractor,
- fingerprintManager = mock<FingerprintManager>(),
+ fingerprintManager = fingerprintManager,
scope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index e0d1d16bcf55..72541540226c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -28,6 +28,7 @@ import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardMediaKeyInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -63,6 +64,7 @@ val Kosmos.bouncerSceneContentViewModel by Fixture {
bouncerHapticPlayer = bouncerHapticPlayer,
keyguardMediaKeyInteractor = keyguardMediaKeyInteractor,
bouncerActionButtonInteractor = bouncerActionButtonInteractor,
+ keyguardDismissActionInteractor = keyguardDismissActionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
index 5485f79629e8..5da6c7b1f2a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.data.repository
import android.app.admin.devicePolicyManager
+import android.content.res.mainResources
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
@@ -27,6 +28,7 @@ val Kosmos.communalSettingsRepository: CommunalSettingsRepository by
Kosmos.Fixture {
CommunalSettingsRepositoryImpl(
bgDispatcher = testDispatcher,
+ resources = mainResources,
featureFlagsClassic = featureFlagsClassic,
secureSettings = fakeSettings,
broadcastDispatcher = broadcastDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 3b175853de7d..1f7f3bc4be2b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -55,7 +55,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
rank: Int = 0,
category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
userId: Int = 0,
- spanY: Int = CommunalContentSize.HALF.span,
+ spanY: Int = CommunalContentSize.FixedSize.HALF.span,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Available(
@@ -87,7 +87,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
componentName = ComponentName.unflattenFromString(componentName)!!,
icon = icon,
user = UserHandle(userId),
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
)
updateListFromDatabase()
}
@@ -143,7 +143,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
appWidgetId = id,
providerInfo = providerInfo,
rank = rank,
- spanY = CommunalContentSize.HALF.span,
+ spanY = CommunalContentSize.FixedSize.HALF.span,
)
updateListFromDatabase()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 1f68195a9acc..ad92b318b0b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import android.content.testableContext
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
@@ -34,6 +35,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.phone.fakeManagedProfileController
@@ -67,6 +69,13 @@ val Kosmos.communalInteractor by Fixture {
val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
+fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) {
+ testableContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ enabled,
+ )
+}
+
suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
if (enabled) {
@@ -76,6 +85,15 @@ suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
}
}
+suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
+ setCommunalV2ConfigEnabled(true)
+ if (enabled) {
+ fakeUserRepository.asMainUser()
+ } else {
+ fakeUserRepository.asDefaultUser()
+ }
+}
+
suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
setCommunalEnabled(available)
with(fakeKeyguardRepository) {
@@ -83,3 +101,12 @@ suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
setKeyguardShowing(available)
}
}
+
+suspend fun Kosmos.setCommunalV2Available(available: Boolean) {
+ setCommunalV2ConfigEnabled(true)
+ setCommunalEnabled(available)
+ with(fakeKeyguardRepository) {
+ setIsEncryptedOrLockdown(!available)
+ setKeyguardShowing(available)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
index 65b18c102a16..5b940f93c4ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
@@ -16,10 +16,11 @@
package com.android.systemui.display.data.repository
+import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeDisplayWindowPropertiesRepository by
- Kosmos.Fixture { FakeDisplayWindowPropertiesRepository() }
+ Kosmos.Fixture { FakeDisplayWindowPropertiesRepository(testableContext) }
var Kosmos.displayWindowPropertiesRepository: DisplayWindowPropertiesRepository by
Kosmos.Fixture { fakeDisplayWindowPropertiesRepository }
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 ddcc92691f5b..3fc60e339543 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
@@ -48,6 +48,7 @@ fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val flow = MutableStateFlow<Set<Display>>(emptySet())
+ private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
private val pendingDisplayFlow =
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
@@ -63,11 +64,13 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
suspend fun addDisplay(display: Display) {
flow.value += display
+ displayIdFlow.value += display.displayId
displayAdditionEventFlow.emit(display)
}
suspend fun removeDisplay(displayId: Int) {
flow.value = flow.value.filter { it.displayId != displayId }.toSet()
+ displayIdFlow.value = displayIdFlow.value.filter { it != displayId }.toSet()
displayRemovalEventFlow.emit(displayId)
}
@@ -83,10 +86,13 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
override val displays: StateFlow<Set<Display>>
get() = flow
+ override val displayIds: StateFlow<Set<Int>>
+ get() = displayIdFlow
+
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
- val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val defaultDisplayOff: Flow<Boolean>
get() = _defaultDisplayOff.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 7fd927654ca6..534ded57eb85 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -16,11 +16,16 @@
package com.android.systemui.display.data.repository
+import android.content.Context
+import android.view.Display
import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.google.common.collect.HashBasedTable
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
-class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository {
+class FakeDisplayWindowPropertiesRepository(private val context: Context) :
+ DisplayWindowPropertiesRepository {
private val properties = HashBasedTable.create<Int, Int, DisplayWindowProperties>()
@@ -29,13 +34,26 @@ class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository
?: DisplayWindowProperties(
displayId = displayId,
windowType = windowType,
- context = mock(),
+ context = contextWithDisplayId(context, displayId),
windowManager = mock(),
layoutInflater = mock(),
)
.also { properties.put(displayId, windowType, it) }
}
+ private fun contextWithDisplayId(context: Context, displayId: Int): Context {
+ val newDisplay = displayWithId(context.display, displayId)
+ return spy(context) {
+ on { getDisplayId() } doReturn displayId
+ on { display } doReturn newDisplay
+ on { displayNoVerify } doReturn newDisplay
+ }
+ }
+
+ private fun displayWithId(display: Display, displayId: Int): Display {
+ return spy(display) { on { getDisplayId() } doReturn displayId }
+ }
+
/** Sets an instance, just for testing purposes. */
fun insert(instance: DisplayWindowProperties) {
properties.put(instance.displayId, instance.windowType, instance)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt
new file mode 100644
index 000000000000..c2466bb4c14d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.display.domain.interactor
+
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.displayWindowPropertiesInteractor by
+ Kosmos.Fixture { DisplayWindowPropertiesInteractorImpl(displayWindowPropertiesRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index 1df3ef48d5a7..1021169c4b3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,9 +17,11 @@
package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
+import java.time.Duration
import java.time.Instant
var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by
Kosmos.Fixture { FakeContextualEducationRepository() }
-var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
+var Kosmos.fakeEduClock: FakeEduClock by
+ Kosmos.Fixture { FakeEduClock(Instant.ofEpochSecond(Duration.ofDays(30).seconds)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
new file mode 100644
index 000000000000..63bfa52e9720
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.globalactions
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+/** Provides a mock */
+val Kosmos.globalActionsDialogLite: GlobalActionsDialogLite by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 2c8581608739..4cb8a416124f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -22,8 +22,10 @@ import android.content.res.mainResources
import android.hardware.input.fakeInputManager
import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.InputGestureDataAdapter
import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
@@ -37,6 +39,7 @@ import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperExclusions
import com.android.systemui.keyboard.shortcut.ui.ShortcutCustomizationDialogStarter
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
@@ -55,10 +58,10 @@ var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSou
Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
var Kosmos.shortcutHelperSystemShortcutsSource: KeyboardShortcutGroupsSource by
- Kosmos.Fixture { SystemShortcutsSource(mainResources) }
+ Kosmos.Fixture { SystemShortcutsSource(mainResources, fakeInputManager.inputManager) }
var Kosmos.shortcutHelperMultiTaskingShortcutsSource: KeyboardShortcutGroupsSource by
- Kosmos.Fixture { MultitaskingShortcutsSource(mainResources) }
+ Kosmos.Fixture { MultitaskingShortcutsSource(mainResources, applicationContext) }
val Kosmos.shortcutHelperStateRepository by
Kosmos.Fixture {
@@ -72,17 +75,23 @@ val Kosmos.shortcutHelperStateRepository by
}
var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by
- Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) }
+ Kosmos.Fixture {
+ InputShortcutsSource(mainResources, windowManager, fakeInputManager.inputManager)
+ }
var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
+val Kosmos.shortcutHelperExclusions by
+ Kosmos.Fixture { ShortcutHelperExclusions(applicationContext) }
+
val Kosmos.shortcutCategoriesUtils by
Kosmos.Fixture {
ShortcutCategoriesUtils(
applicationContext,
backgroundCoroutineContext,
fakeInputManager.inputManager,
+ shortcutHelperExclusions,
)
}
@@ -104,16 +113,21 @@ val Kosmos.defaultShortcutCategoriesRepository by
val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) }
+val Kosmos.inputGestureDataAdapter by Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext)}
+
+val Kosmos.customInputGesturesRepository by
+ Kosmos.Fixture { CustomInputGesturesRepository(userTracker, testDispatcher) }
+
val Kosmos.customShortcutCategoriesRepository by
Kosmos.Fixture {
CustomShortcutCategoriesRepository(
shortcutHelperStateRepository,
- userTracker,
applicationCoroutineScope,
testDispatcher,
shortcutCategoriesUtils,
- applicationContext,
- inputGestureMaps,
+ inputGestureDataAdapter,
+ customInputGesturesRepository,
+ fakeInputManager.inputManager,
)
}
@@ -141,7 +155,10 @@ val Kosmos.shortcutHelperStateInteractor by
val Kosmos.shortcutHelperCategoriesInteractor by
Kosmos.Fixture {
- ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) {
+ ShortcutHelperCategoriesInteractor(
+ context = applicationContext,
+ defaultShortcutCategoriesRepository,
+ ) {
customShortcutCategoriesRepository
}
}
@@ -149,6 +166,7 @@ val Kosmos.shortcutHelperCategoriesInteractor by
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
ShortcutHelperViewModel(
+ applicationContext,
mockRoleManager,
userTracker,
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index bd841ab2e801..09f5fd79eeca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -17,14 +17,12 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.logging.KeyguardLogger
-import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,8 +36,6 @@ val Kosmos.keyguardDismissActionInteractor by
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
deviceUnlockedInteractor = { deviceUnlockedInteractor },
- powerInteractor = powerInteractor,
- alternateBouncerInteractor = alternateBouncerInteractor,
shadeInteractor = { shadeInteractor },
keyguardInteractor = { keyguardInteractor },
sceneInteractor = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index ace11573c7c6..339210c07437 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -38,8 +38,8 @@ val Kosmos.keyguardDismissInteractor by
primaryBouncerInteractor = primaryBouncerInteractor,
selectedUserInteractor = selectedUserInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
- trustRepository = trustRepository,
alternateBouncerInteractor = alternateBouncerInteractor,
+ trustRepository = trustRepository,
powerInteractor = powerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 1556058d51ba..7ee9d84d84fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -8,7 +8,10 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
import com.android.systemui.util.mockito.mock
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -58,3 +61,27 @@ fun Kosmos.runCurrent() = testScope.runCurrent()
fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
fun <T> Kosmos.collectValues(flow: Flow<T>): FlowValue<List<T>> = testScope.collectValues(flow)
+
+/**
+ * Retrieve the current value of this [StateFlow] safely. Needs a [TestScope] in order to make sure
+ * that all pending tasks have run before returning a value. Tests that directly access
+ * [StateFlow.value] may be incorrect, since the value returned may be stale if the current test
+ * dispatcher is a [StandardTestDispatcher].
+ *
+ * If you want to assert on a [Flow] that is not a [StateFlow], please use
+ * [TestScope.collectLastValue], to make sure that the desired value is captured when emitted.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T {
+ val values = mutableListOf<T>()
+ val job = backgroundScope.launch { stateFlow.collect(values::add) }
+ runCurrent()
+ job.cancel()
+ // StateFlow should always have at least one value
+ return values.last()
+}
+
+/** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */
+fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T {
+ return testScope.currentValue(stateFlow)
+}
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 3d60abf59d62..41cfceaa5e38 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
@@ -66,6 +66,7 @@ import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeLayoutParams
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
@@ -78,6 +79,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore
import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.phone.scrimController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
@@ -132,6 +134,8 @@ class KosmosJavaAdapter() {
val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
val statusBarStateController by lazy { kosmos.statusBarStateController }
val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
+ val shadeLayoutParams by lazy { kosmos.shadeLayoutParams }
+ val autoHideControllerStore by lazy { kosmos.fakeAutoHideControllerStore }
val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
val sceneInteractor by lazy { kosmos.sceneInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7a04aa288dce..7964c1114be5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.media.controls.data.repository
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.media.controls.util.mediaSmartspaceLogger
-import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.util.time.systemClock
val Kosmos.mediaFilterRepository by
@@ -27,7 +26,6 @@ val Kosmos.mediaFilterRepository by
MediaFilterRepository(
applicationContext = applicationContext,
systemClock = systemClock,
- configurationController = configurationController,
smartspaceLogger = mediaSmartspaceLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
index 8aa7a03710cb..d5637cbe36b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.mediarouter.data.repository
+import android.media.projection.StopReason
import com.android.systemui.statusbar.policy.CastDevice
import kotlinx.coroutines.flow.MutableStateFlow
@@ -25,7 +26,7 @@ class FakeMediaRouterRepository : MediaRouterRepository {
var lastStoppedDevice: CastDevice? = null
private set
- override fun stopCasting(device: CastDevice) {
+ override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
lastStoppedDevice = device
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 10b073e8f331..2e6d8ed5aa5b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -28,6 +28,7 @@ import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.mockito.mock
@@ -49,3 +50,6 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by
{ alternateBouncerInteractor },
)
}
+
+var Kosmos.fakeStatusBarStateController: SysuiStatusBarStateController by
+ Kosmos.Fixture { FakeStatusBarStateController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
new file mode 100644
index 000000000000..0bf801b35ad1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.external.tileServiceRequestControllerBuilder
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.qsHostAdapter by
+ Kosmos.Fixture {
+ QSHostAdapter(
+ currentTilesInteractor,
+ applicationContext,
+ tileServiceRequestControllerBuilder,
+ applicationCoroutineScope,
+ dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index c574463eb258..d71bc310b0ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteracto
import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
@@ -56,6 +57,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by
disableFlagsInteractor,
keyguardTransitionInteractor,
largeScreenShadeInterpolator,
+ shadeInteractor,
configurationInteractor,
largeScreenHeaderHelper,
tileSquishinessInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt
new file mode 100644
index 000000000000..296623462a54
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import com.android.app.iUriGrantsManager
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.qsHostAdapter
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.commandline.commandRegistry
+import org.mockito.kotlin.mock
+
+val Kosmos.tileServiceRequestControllerBuilder by
+ Kosmos.Fixture {
+ TileServiceRequestController.Builder(
+ commandQueue,
+ commandRegistry,
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
+ )
+ }
+
+val Kosmos.tileServiceRequestController by
+ Kosmos.Fixture {
+ TileServiceRequestController(
+ qsHostAdapter,
+ commandQueue,
+ commandRegistry,
+ TileRequestDialogEventLogger(uiEventLoggerFake, instanceIdSequenceFake),
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
+ { mock() },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
new file mode 100644
index 000000000000..1e0ebe44ba2a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.qs.external.TileData
+import org.mockito.Answers
+import org.mockito.kotlin.mock
+
+class FakeTileRequestDialogComposeDelegateFactory : TileRequestDialogComposeDelegate.Factory {
+ lateinit var tileData: TileData
+ lateinit var clickListener: DialogInterface.OnClickListener
+
+ override fun create(
+ tileData: TileData,
+ dialogListener: DialogInterface.OnClickListener,
+ ): TileRequestDialogComposeDelegate {
+ this.tileData = tileData
+ this.clickListener = dialogListener
+ return mock(defaultAnswer = Answers.RETURNS_MOCKS)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt
new file mode 100644
index 000000000000..030af61e5569
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.tileRequestDialogViewModelFactory
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+
+var Kosmos.tileRequestDialogComposeDelegateFactory by
+ Kosmos.Fixture<TileRequestDialogComposeDelegate.Factory> {
+ object : TileRequestDialogComposeDelegate.Factory {
+ override fun create(
+ tiledata: TileData,
+ dialogListener: DialogInterface.OnClickListener,
+ ): TileRequestDialogComposeDelegate {
+ return TileRequestDialogComposeDelegate(
+ systemUIDialogFactory,
+ tileRequestDialogViewModelFactory,
+ tiledata,
+ dialogListener,
+ )
+ }
+ }
+ }
+
+val TileRequestDialogComposeDelegate.Factory.fake: FakeTileRequestDialogComposeDelegateFactory
+ get() = this as FakeTileRequestDialogComposeDelegateFactory
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt
new file mode 100644
index 000000000000..7b1797db24f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external.ui.viewmodel
+
+import android.content.Context
+import com.android.app.iUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.external.TileData
+
+val Kosmos.tileRequestDialogViewModelFactory by
+ Kosmos.Fixture {
+ object : TileRequestDialogViewModel.Factory {
+ override fun create(
+ dialogContext: Context,
+ tileData: TileData,
+ ): TileRequestDialogViewModel {
+ return TileRequestDialogViewModel(
+ iUriGrantsManager,
+ testDispatcher,
+ dialogContext,
+ tileData,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index 4acedaa9044d..322b412fb054 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,5 +18,4 @@ package com.android.systemui.qs.panels.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
- Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
+var Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c9516429553b..40c3c96ff241 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -20,10 +20,19 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.paginatedGridLayout
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
val Kosmos.gridLayoutTypeInteractor by
- Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
+ Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository, shadeModeInteractor) }
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
- Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+ Kosmos.Fixture {
+ mapOf(
+ Pair(InfiniteGridLayoutType, infiniteGridLayout),
+ Pair(PaginatedGridLayoutType, paginatedGridLayout),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
new file mode 100644
index 000000000000..f412d98a9d4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModelFactory
+
+val Kosmos.paginatedGridLayout by
+ Kosmos.Fixture { PaginatedGridLayout(paginatedGridViewModelFactory, infiniteGridLayout) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
index 6fe860cfd0d3..f57806c8b678 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
index 33227a4fcc62..86c3add09577 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
@@ -23,8 +23,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.panels.domain.interactor.tilesAvailabilityInteractor
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 5c71ba2f8bbd..128cfcad5c45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory
val Kosmos.paginatedGridViewModel by
Kosmos.Fixture {
@@ -33,3 +34,10 @@ val Kosmos.paginatedGridViewModel by
falsingInteractor,
)
}
+
+val Kosmos.paginatedGridViewModelFactory by
+ Kosmos.Fixture {
+ object : PaginatedGridViewModel.Factory {
+ override fun create() = paginatedGridViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 9481fcac97d6..e79d213a8ffb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,7 +20,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
index b8d3ff425f20..8ae1332c387a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
val Kosmos.editModeButtonViewModelFactory by
Kosmos.Fixture {
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
new file mode 100644
index 000000000000..52d8a3aac836
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
+
+import android.content.applicationContext
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.globalactions.globalActionsDialogLite
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.footerActionsInteractor
+
+val Kosmos.toolbarViewModelFactory by
+ Kosmos.Fixture {
+ object : ToolbarViewModel.Factory {
+ override fun create(): ToolbarViewModel {
+ return ToolbarViewModel(
+ editModeButtonViewModelFactory,
+ footerActionsInteractor,
+ { globalActionsDialogLite },
+ falsingInteractor,
+ applicationContext,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 597d52dcb299..bc1c60c33d71 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles.base.interactor
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.qs.FakeTileDetailsViewModel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -31,4 +33,7 @@ class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
override suspend fun handleInput(input: QSTileInput<T>) {
mutex.withLock { mutableInputs.add(input) }
}
+
+ override var detailsViewModel: TileDetailsViewModel? =
+ FakeTileDetailsViewModel("FakeQSTileUserActionInteractor")
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 2ecfb454a6f0..3c37101cfe66 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
import javax.inject.Provider
@@ -26,5 +27,6 @@ val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
ModesTileUserActionInteractor(
qsTileIntentUserInputHandler,
Provider { modesDialogDelegate }.get(),
+ zenModeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index 6afc0d803f8d..aa6ce9a433df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.toolbarViewModelFactory
val Kosmos.quickSettingsContainerViewModelFactory by
Kosmos.Fixture {
@@ -36,6 +37,7 @@ val Kosmos.quickSettingsContainerViewModelFactory by
tileGridViewModel,
editModeViewModel,
detailsViewModel,
+ toolbarViewModelFactory,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 3300c96b87fd..0eca818e9aac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -13,6 +13,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
@@ -60,6 +61,7 @@ var Kosmos.sceneContainerConfig by Fixture {
SceneContainerConfig(
sceneKeys = sceneKeys,
initialSceneKey = initialSceneKey,
+ transitions = SceneContainerTransitions,
overlayKeys = overlayKeys,
navigationDistances = navigationDistances,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
new file mode 100644
index 000000000000..5c91dc80b3d4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.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.scene.ui.view
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
index 30b4763118a7..4c9e1740b3b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord.data.repository
+import android.media.projection.StopReason
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -25,7 +26,7 @@ class FakeScreenRecordRepository : ScreenRecordRepository {
var stopRecordingInvoked = false
- override suspend fun stopRecording() {
+ override suspend fun stopRecording(@StopReason stopReason: Int) {
stopRecordingInvoked = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index dbaa0b1d5bf6..7488397dde1f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -32,3 +32,6 @@ val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
bgScope = testScope.backgroundScope,
)
}
+
+val Kosmos.fakeShadeDisplaysRepository: FakeShadeDisplayRepository by
+ Kosmos.Fixture { FakeShadeDisplayRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
new file mode 100644
index 000000000000..db4df38e038a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.mockedContext
+import android.view.mockWindowManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.ui.view.mockShadeRootView
+import com.android.systemui.shade.ShadeWindowLayoutParams
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import java.util.Optional
+
+val Kosmos.shadeLayoutParams by Kosmos.Fixture {
+ ShadeWindowLayoutParams.create(mockedContext)
+}
+val Kosmos.shadeDisplaysInteractor by
+ Kosmos.Fixture {
+ ShadeDisplaysInteractor(
+ Optional.of(mockShadeRootView),
+ fakeShadeDisplaysRepository,
+ mockedContext,
+ shadeLayoutParams,
+ mockWindowManager,
+ testScope.backgroundScope,
+ testScope.backgroundScope.coroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
index fcd14d8512fb..d8d4d2b65eff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
@@ -20,12 +20,14 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.ongoingCallInteractor
val Kosmos.callChipInteractor: CallChipInteractor by
Kosmos.Fixture {
CallChipInteractor(
scope = applicationCoroutineScope,
repository = ongoingCallRepository,
+ ongoingCallInteractor = ongoingCallInteractor,
logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
index 9197dcdc3f68..2d88ae8e926a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.core
import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.phone.AutoHideController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository
import kotlinx.coroutines.CoroutineScope
@@ -36,6 +37,7 @@ class FakeStatusBarOrchestratorFactory : StatusBarOrchestrator.Factory {
statusBarModeRepository: StatusBarModePerDisplayRepository,
statusBarInitializer: StatusBarInitializer,
statusBarWindowController: StatusBarWindowController,
+ autoHideController: AutoHideController,
): StatusBarOrchestrator =
mock<StatusBarOrchestrator>().also { createdOrchestrators[displayId] = it }
}
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 28edae7c3689..8c37bd739bc5 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
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.data.repository.privacyDotWindowController
import com.android.systemui.statusbar.data.repository.statusBarModeRepository
import com.android.systemui.statusbar.mockNotificationRemoteInputManager
import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.phone.multiDisplayAutoHideControllerStore
import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
import com.android.systemui.statusbar.window.fakeStatusBarWindowController
@@ -50,9 +51,9 @@ val Kosmos.statusBarOrchestrator by
fakeStatusBarInitializer,
fakeStatusBarWindowController,
applicationCoroutineScope.coroutineContext,
+ mockAutoHideController,
mockDemoModeController,
mockPluginDependencyProvider,
- mockAutoHideController,
mockNotificationRemoteInputManager,
{ mockNotificationShadeWindowViewController },
mockShadeSurface,
@@ -80,6 +81,7 @@ val Kosmos.multiDisplayStatusBarStarter by
statusBarInitializerStore,
statusBarWindowControllerStore,
statusBarInitializerStore,
+ multiDisplayAutoHideControllerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 8712b02ec884..22f8767e1d55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -34,12 +34,12 @@ class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositor
const val DISPLAY_ID = Display.DEFAULT_DISPLAY
}
- override val defaultDisplay: FakeStatusBarModePerDisplayRepository =
- FakeStatusBarModePerDisplayRepository()
+ private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarModePerDisplayRepository>()
- override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
- return defaultDisplay
- }
+ override val defaultDisplay: FakeStatusBarModePerDisplayRepository = forDisplay(DISPLAY_ID)
+
+ override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository =
+ perDisplayRepos.computeIfAbsent(displayId) { FakeStatusBarModePerDisplayRepository() }
}
class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt
new file mode 100644
index 000000000000..fa5bd7a90fcf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import org.mockito.kotlin.mock
+
+var Kosmos.dynamicPrivacyController: DynamicPrivacyController by
+ Kosmos.Fixture {
+ DynamicPrivacyController(
+ notificationLockscreenUserManager,
+ keyguardStateController,
+ statusBarStateController,
+ )
+ }
+
+var Kosmos.mockDynamicPrivacyController: DynamicPrivacyController by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt
new file mode 100644
index 000000000000..4a249a8591e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.notification.collection.coordinator
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.notification.dynamicPrivacyController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+var Kosmos.sensitiveContentCoordinator: SensitiveContentCoordinator by
+ Kosmos.Fixture {
+ SensitiveContentCoordinatorImpl(
+ dynamicPrivacyController,
+ notificationLockscreenUserManager,
+ keyguardUpdateMonitor,
+ statusBarStateController,
+ keyguardStateController,
+ selectedUserInteractor,
+ sensitiveNotificationProtectionController,
+ deviceEntryInteractor,
+ sceneInteractor,
+ testScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 2ec801620212..c6ae15df6859 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -29,7 +29,6 @@ fun activeNotificationModel(
key: String,
groupKey: String? = null,
whenTime: Long = 0L,
- isPromoted: Boolean = false,
isAmbient: Boolean = false,
isRowDismissed: Boolean = false,
isSilent: Boolean = false,
@@ -53,7 +52,6 @@ fun activeNotificationModel(
key = key,
groupKey = groupKey,
whenTime = whenTime,
- isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index 067193fb7aa9..f7acae9846df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,13 +19,8 @@ package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
val Kosmos.renderNotificationListInteractor by
Kosmos.Fixture {
- RenderNotificationListInteractor(
- activeNotificationListRepository,
- sectionStyleProvider,
- promotedNotificationsProvider,
- )
+ RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
}
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 2d3f68faf4b7..7126933154df 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
@@ -34,7 +34,7 @@ import com.android.systemui.TestableDependency
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.util.MediaFeatureFlag
@@ -92,8 +92,6 @@ import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
@@ -106,7 +104,7 @@ import org.mockito.Mockito
class ExpandableNotificationRowBuilder(
private val context: Context,
dependency: TestableDependency,
- private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(),
+ featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(),
) {
private val mMockLogger: ExpandableNotificationRowLogger
@@ -137,7 +135,7 @@ class ExpandableNotificationRowBuilder(
featureFlags.setDefault(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE)
featureFlags.setDefault(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)
- dependency.injectTestDependency(FeatureFlags::class.java, featureFlags)
+ dependency.injectTestDependency(FeatureFlagsClassic::class.java, featureFlags)
dependency.injectMockDependency(NotificationMediaManager::class.java)
dependency.injectMockDependency(NotificationShadeWindowController::class.java)
dependency.injectMockDependency(MediaOutputDialogManager::class.java)
@@ -299,7 +297,7 @@ class ExpandableNotificationRowBuilder(
}
private fun getNotifRemoteViewsFactoryContainer(
- featureFlags: FeatureFlags
+ featureFlags: FeatureFlagsClassic
): NotifRemoteViewsFactoryContainer {
return NotifRemoteViewsFactoryContainerImpl(
featureFlags,
@@ -380,7 +378,6 @@ class ExpandableNotificationRowBuilder(
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
- Optional.of(Mockito.mock(BubblesManager::class.java, STUB_ONLY)),
Mockito.mock(NotificationGutsManager::class.java, STUB_ONLY),
mDismissibilityProvider,
Mockito.mock(MetricsLogger::class.java, STUB_ONLY),
@@ -388,11 +385,10 @@ class ExpandableNotificationRowBuilder(
Mockito.mock(ColorUpdateLogger::class.java, STUB_ONLY),
mSmartReplyConstants,
mSmartReplyController,
- featureFlags,
Mockito.mock(IStatusBarService::class.java, STUB_ONLY),
Mockito.mock(UiEventLogger::class.java, STUB_ONLY),
)
- row.setAboveShelfChangedListener { aboveShelf: Boolean -> }
+ row.setAboveShelfChangedListener {}
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags)
inflateAndWait(entry)
return row
@@ -410,7 +406,7 @@ class ExpandableNotificationRowBuilder(
private const val PKG = "com.android.systemui"
private const val UID = 1000
private val USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser())
- private val INFLATION_FLAGS =
+ private const val INFLATION_FLAGS =
FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
private const val IS_CONVERSATION_FLAG = "test.isConversation"
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 7fbf4e495feb..d1619b7959f2 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
@@ -23,6 +23,7 @@ import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
@@ -71,6 +72,8 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
shadeInteractor = shadeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ alternateBouncerToPrimaryBouncerTransitionViewModel =
+ alternateBouncerToPrimaryBouncerTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
index 951ae59ebcf1..b99e93abfa30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.os.Handler
+import android.view.Display
import android.view.IWindowManager
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
@@ -28,8 +29,6 @@ import org.mockito.Mockito.mock
val Kosmos.mockAutoHideController: AutoHideController by
Kosmos.Fixture { mock(AutoHideController::class.java) }
-var Kosmos.autoHideController by Kosmos.Fixture { mockAutoHideController }
-
val Kosmos.fakeAutoHideControllerFactory by Kosmos.Fixture { FakeAutoHideControllerFactory() }
val Kosmos.multiDisplayAutoHideControllerStore by
@@ -42,6 +41,8 @@ val Kosmos.multiDisplayAutoHideControllerStore by
)
}
+val Kosmos.fakeAutoHideControllerStore by Kosmos.Fixture { FakeAutoHideControllerStore() }
+
class FakeAutoHideControllerFactory :
AutoHideControllerImpl.Factory(mock(Handler::class.java), mock(IWindowManager::class.java)) {
@@ -49,3 +50,15 @@ class FakeAutoHideControllerFactory :
return mock(AutoHideControllerImpl::class.java)
}
}
+
+class FakeAutoHideControllerStore : AutoHideControllerStore {
+
+ private val perDisplayMocks = mutableMapOf<Int, AutoHideController>()
+
+ override val defaultDisplay: AutoHideController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): AutoHideController {
+ return perDisplayMocks.computeIfAbsent(displayId) { mock(AutoHideController::class.java) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
new file mode 100644
index 000000000000..51fb36fb2ead
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.phone.ongoingcall.domain.interactor
+
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+val Kosmos.ongoingCallInteractor: OngoingCallInteractor by
+ Kosmos.Fixture {
+ OngoingCallInteractor(
+ scope = applicationCoroutineScope,
+ activeNotificationsInteractor = activeNotificationsInteractor,
+ activityManagerRepository = activityManagerRepository,
+ logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
index 3963d7c5be63..766b280334c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -23,5 +23,6 @@ import com.android.systemui.statusbar.StatusBarIconView
fun inCallModel(
startTimeMs: Long,
notificationIcon: StatusBarIconView? = null,
- intent: PendingIntent? = null
-) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent)
+ intent: PendingIntent? = null,
+ notificationKey: String = "test",
+) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey)
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 03e4c894c2f2..eb17237afbb5 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
@@ -26,6 +26,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
@@ -35,6 +36,7 @@ val Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
collapsedStatusBarInteractor,
lightsOutInteractor,
activeNotificationsInteractor,
+ headsUpNotificationInteractor,
keyguardTransitionInteractor,
keyguardInteractor,
sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
index 2df0c7a5386e..da6b2ae46d2d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.media.projection.StopReason
import java.io.PrintWriter
class FakeCastController : CastController {
@@ -45,7 +46,7 @@ class FakeCastController : CastController {
override fun startCasting(device: CastDevice?) {}
- override fun stopCasting(device: CastDevice?) {
+ override fun stopCasting(device: CastDevice?, @StopReason stopReason: Int) {
lastStoppedDevice = device
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
new file mode 100644
index 000000000000..8f9184d1dbf3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.policy
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.sensitiveNotificationProtectionController: SensitiveNotificationProtectionController by
+ Kosmos.Fixture { mockSensitiveNotificationProtectionController }
+val Kosmos.mockSensitiveNotificationProtectionController:
+ SensitiveNotificationProtectionController by
+ Kosmos.Fixture { mock<SensitiveNotificationProtectionController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 68d08e285c53..bbccbb10923a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -20,6 +20,7 @@ import android.content.applicationContext
import com.android.settingslib.notification.modes.zenIconLoader
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
@@ -32,6 +33,7 @@ val Kosmos.zenModeInteractor by Fixture {
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
+ backgroundScope = backgroundScope,
iconLoader = zenIconLoader,
deviceProvisioningRepository = deviceProvisioningRepository,
userSetupRepository = userSetupRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt
new file mode 100644
index 000000000000..f12089a08488
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.touchpad.ui.gesture
+
+import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
+import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
+
+class FakeVelocityTracker : VelocityTracker {
+
+ private var fakeVelocity = Velocity(0f)
+
+ override fun calculateVelocity(): Velocity {
+ return fakeVelocity
+ }
+
+ override fun accept(event: MotionEvent) {}
+
+ fun setVelocity(velocity: Velocity) {
+ fakeVelocity = velocity
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt
new file mode 100644
index 000000000000..3b61e2191d8c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.touchpad.ui.gesture
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.fakeVelocityTracker: FakeVelocityTracker by Kosmos.Fixture { FakeVelocityTracker() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
index 2249bc0b667f..857dc8584be9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
@@ -16,6 +16,7 @@
package com.android.systemui.utils.leaks;
+import android.media.projection.StopReason;
import android.testing.LeakCheck;
import com.android.systemui.statusbar.policy.CastController;
@@ -51,7 +52,7 @@ public class LeakCheckerCastController extends BaseLeakChecker<Callback> impleme
}
@Override
- public void stopCasting(CastDevice device) {
+ public void stopCasting(CastDevice device, @StopReason int stopReason) {
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt
new file mode 100644
index 000000000000..ddd001400879
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.volume.dialog.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.volumeDialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor by
+ Kosmos.Fixture {
+ VolumeDialogSafetyWarningInteractor(
+ volumeDialogStateInteractor,
+ volumeDialogVisibilityInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
index c8ba551c518a..34661ced71b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
import com.android.systemui.volume.dialog.shared.volumeDialogLogger
@@ -31,7 +32,8 @@ val Kosmos.volumeDialogRingerDrawerViewModel by
applicationContext = applicationContext,
backgroundDispatcher = testDispatcher,
coroutineScope = applicationCoroutineScope,
- interactor = volumeDialogRingerInteractor,
+ soundPolicyInteractor = notificationsSoundPolicyInteractor,
+ ringerInteractor = volumeDialogRingerInteractor,
vibrator = vibratorHelper,
volumeDialogLogger = volumeDialogLogger,
visibilityInteractor = volumeDialogVisibilityInteractor,
diff --git a/packages/Vcn/TEST_MAPPING b/packages/Vcn/TEST_MAPPING
index bde88fe9b717..9722a838ab8e 100644
--- a/packages/Vcn/TEST_MAPPING
+++ b/packages/Vcn/TEST_MAPPING
@@ -1,4 +1,12 @@
{
+ "presubmit": [
+ {
+ "name": "FrameworksVcnTests"
+ },
+ {
+ "name": "CtsVcnTestCases"
+ }
+ ],
"postsubmit": [
{
"name": "FrameworksVcnTests"
diff --git a/packages/Vcn/flags/Android.bp b/packages/Vcn/flags/Android.bp
new file mode 100644
index 000000000000..3943c6f7fe24
--- /dev/null
+++ b/packages/Vcn/flags/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+ default_team: "trendy_team_enigma",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+ name: "android.net.vcn.flags-aconfig",
+ package: "android.net.vcn",
+ container: "com.android.tethering",
+ exportable: true,
+ srcs: [
+ "flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.net.vcn.flags-aconfig-java-export",
+ aconfig_declarations: "android.net.vcn.flags-aconfig",
+ mode: "exported",
+ min_sdk_version: "35",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/java/android/net/vcn/flags.aconfig b/packages/Vcn/flags/flags.aconfig
index b461f95fec53..b461f95fec53 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/packages/Vcn/flags/flags.aconfig
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index be64bb1ae404..c3121162b7f2 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -19,6 +19,18 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "vcn-utils-platform-sources",
+ srcs: [
+ "src/android/net/vcn/persistablebundleutils/**/*.java",
+ "src/android/net/vcn/util/**/*.java",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/base", // For VpnProfile.java and Vpn.java
+ ],
+}
+
java_defaults {
name: "framework-connectivity-b-defaults",
sdk_version: "module_current",
@@ -27,7 +39,25 @@ java_defaults {
srcs: [
"src/**/*.java",
+ "src/**/*.aidl",
+ ],
+
+ libs: [
+ "android.net.ipsec.ike.stubs.module_lib",
+ "app-compat-annotations",
+ "framework-wifi.stubs.module_lib",
+ "unsupportedappusage",
],
+ static_libs: [
+ //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+ "android.net.vcn.flags-aconfig-java-export",
+ ],
+ aidl: {
+ include_dirs: [
+ // For connectivity-framework classes such as Network.aidl, NetworkCapabilities.aidl
+ "packages/modules/Connectivity/framework/aidl-export",
+ ],
+ },
}
java_sdk_library {
@@ -36,9 +66,72 @@ java_sdk_library {
"framework-connectivity-b-defaults",
],
+ //TODO: b/375213246 Use "framework-connectivity-jarjar-rules" when VCN is
+ // in mainline
+ jarjar_rules: "framework-vcn-jarjar-rules.txt",
+
permitted_packages: [
+ "android.net",
+ "android.net.vcn",
+ "com.android.server.vcn.util",
+
+ ],
+ api_packages: [
+ "android.net",
"android.net.vcn",
],
- // TODO: b/375213246 Expose this library to Tethering module
+ // Allow VCN APIs to reference APIs in IKE and Connectivity
+ stub_only_libs: [
+ "android.net.ipsec.ike.stubs.module_lib",
+ "framework-connectivity.stubs.module_lib",
+ ],
+
+ // To use non-jarjard names of utilities such as android.util.IndentingPrintWriter
+ impl_only_libs: [
+ "framework-connectivity-pre-jarjar",
+ ],
+
+ aconfig_declarations: [
+ //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+ "android.net.vcn.flags-aconfig-java-export",
+ ],
+
+ impl_library_visibility: [
+ // Using for test only
+ "//cts/tests/netlegacy22.api",
+ "//cts/tests/tests/vcn",
+ "//external/sl4a:__subpackages__",
+ "//frameworks/base/core/tests/bandwidthtests",
+ "//frameworks/base/core/tests/benchmarks",
+ "//frameworks/base/core/tests/utillib",
+ "//frameworks/base/services/tests/VpnTests",
+ "//frameworks/base/tests/vcn",
+ "//frameworks/opt/telephony/tests/telephonytests",
+ "//packages/modules/CaptivePortalLogin/tests",
+ "//packages/modules/Connectivity/staticlibs/testutils",
+ "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
+ "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+ "//packages/modules/Connectivity/tests:__subpackages__",
+ "//packages/modules/Connectivity/thread/tests:__subpackages__",
+ "//packages/modules/IPsec/tests/iketests",
+ "//packages/modules/NetworkStack",
+ "//packages/modules/NetworkStack/tests:__subpackages__",
+ "//packages/modules/Wifi/service/tests/wifitests",
+ ],
+
+ apex_available: [
+ // TODO: b/374174952 Remove it when VCN modularization is released
+ "//apex_available:platform",
+
+ "com.android.tethering",
+ ],
+}
+
+java_library {
+ name: "framework-connectivity-b-pre-jarjar",
+ defaults: ["framework-connectivity-b-defaults"],
+ libs: [
+ "framework-connectivity-pre-jarjar",
+ ],
}
diff --git a/packages/Vcn/framework-b/api/current.txt b/packages/Vcn/framework-b/api/current.txt
index d802177e249b..831b74158e67 100644
--- a/packages/Vcn/framework-b/api/current.txt
+++ b/packages/Vcn/framework-b/api/current.txt
@@ -1 +1,123 @@
// Signature format: 2.0
+package android.net.vcn {
+
+ public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+ method public int getCbs();
+ method public int getDun();
+ method public int getIms();
+ method public int getInternet();
+ method public int getMms();
+ method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+ method public int getOpportunistic();
+ method public int getRcs();
+ method public int getRoaming();
+ method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+ }
+
+ public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+ ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+ method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+ }
+
+ public final class VcnConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+ method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+ }
+
+ public static final class VcnConfig.Builder {
+ ctor public VcnConfig.Builder(@NonNull android.content.Context);
+ method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+ method @NonNull public android.net.vcn.VcnConfig build();
+ method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
+ }
+
+ public final class VcnGatewayConnectionConfig {
+ method @NonNull public int[] getExposedCapabilities();
+ method @NonNull public String getGatewayConnectionName();
+ method @IntRange(from=0x500) public int getMaxMtu();
+ method public int getMinUdpPort4500NatTimeoutSeconds();
+ method @NonNull public long[] getRetryIntervalsMillis();
+ method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+ method public boolean hasGatewayOption(int);
+ method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
+ field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
+ field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
+ }
+
+ public static final class VcnGatewayConnectionConfig.Builder {
+ ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+ method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
+ }
+
+ public class VcnManager {
+ method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+ method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
+ method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+ method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+ method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+ field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
+ field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
+ field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
+ field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
+ field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
+ field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
+ field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
+ }
+
+ public abstract static class VcnManager.VcnStatusCallback {
+ ctor public VcnManager.VcnStatusCallback();
+ method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
+ method public abstract void onStatusChanged(int);
+ }
+
+ public abstract class VcnUnderlyingNetworkTemplate {
+ method public int getMetered();
+ method public int getMinEntryDownstreamBandwidthKbps();
+ method public int getMinEntryUpstreamBandwidthKbps();
+ method public int getMinExitDownstreamBandwidthKbps();
+ method public int getMinExitUpstreamBandwidthKbps();
+ field public static final int MATCH_ANY = 0; // 0x0
+ field public static final int MATCH_FORBIDDEN = 2; // 0x2
+ field public static final int MATCH_REQUIRED = 1; // 0x1
+ }
+
+ public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+ method @NonNull public java.util.Set<java.lang.String> getSsids();
+ }
+
+ public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+ ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+ method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+ }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/module-lib-current.txt b/packages/Vcn/framework-b/api/module-lib-current.txt
index d802177e249b..8961b2830f86 100644
--- a/packages/Vcn/framework-b/api/module-lib-current.txt
+++ b/packages/Vcn/framework-b/api/module-lib-current.txt
@@ -1 +1,28 @@
// Signature format: 2.0
+package android.net {
+
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
+ }
+
+}
+
+package android.net.vcn {
+
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
+ }
+
+ @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
+ ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
+ method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+ }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/system-current.txt b/packages/Vcn/framework-b/api/system-current.txt
index d802177e249b..9c5a67701b74 100644
--- a/packages/Vcn/framework-b/api/system-current.txt
+++ b/packages/Vcn/framework-b/api/system-current.txt
@@ -1 +1,23 @@
// Signature format: 2.0
+package android.net.vcn {
+
+ public class VcnManager {
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+ }
+
+ public static interface VcnManager.VcnNetworkPolicyChangeListener {
+ method public void onPolicyChanged();
+ }
+
+ public final class VcnNetworkPolicyResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+ method public boolean isTeardownRequested();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
+ }
+
+}
+
diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
new file mode 100644
index 000000000000..757043bdbbc0
--- /dev/null
+++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.net.vcn.persistablebundleutils.** android.net.vcn.module.repackaged.persistablebundleutils.@1
+rule android.net.vcn.util.** android.net.vcn.module.repackaged.util.@1 \ No newline at end of file
diff --git a/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java b/packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
index 1f0fa92d7976..1f0fa92d7976 100644
--- a/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
+++ b/packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
index e16f6b167750..e16f6b167750 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
index 11bc443c9dd6..11bc443c9dd6 100644
--- a/core/java/android/net/vcn/IVcnStatusCallback.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
diff --git a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
index 62de8216ce54..62de8216ce54 100644
--- a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index c0398ce1fcf1..ded94159a945 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -23,18 +23,19 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.net.NetworkCapabilities;
import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -44,7 +45,6 @@ import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/core/java/android/net/vcn/VcnConfig.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
index 67006a42a701..67006a42a701 100644
--- a/core/java/android/net/vcn/VcnConfig.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
diff --git a/core/java/android/net/vcn/VcnConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
index a27e9230d473..0d0efb2f73f9 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
@@ -18,10 +18,10 @@ package android.net.vcn;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -29,6 +29,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -37,7 +38,6 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
index b270062cbffc..067144e6f474 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -32,12 +32,12 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/android/net/vcn/VcnManager.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
index f275714e2cf5..594bbb8a2015 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
@@ -334,7 +334,7 @@ public class VcnManager {
* @param executor the Executor that will be used for invoking all calls to the specified
* Listener
* @param listener the VcnUnderlyingNetworkPolicyListener to be added
- * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+ * @throws SecurityException if the caller does not have the required permission
* @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already
* registered
* @hide
@@ -423,7 +423,7 @@ public class VcnManager {
* @param executor the Executor that will be used for invoking all calls to the specified
* Listener
* @param listener the VcnNetworkPolicyChangeListener to be added
- * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+ * @throws SecurityException if the caller does not have the required permission
* @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already
* registered
* @hide
@@ -455,7 +455,7 @@ public class VcnManager {
* <p>If the specified listener is not currently registered, this is a no-op.
*
* @param listener the VcnNetworkPolicyChangeListener that will be removed
- * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+ * @throws SecurityException if the caller does not have the required permission
* @hide
*/
@SystemApi
@@ -489,7 +489,7 @@ public class VcnManager {
* policy result for this Network.
* @param linkProperties the LinkProperties to be used in determining the Network policy result
* for this Network.
- * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+ * @throws SecurityException if the caller does not have the required permission
* @return the {@link VcnNetworkPolicyResult} to be used for this Network.
* @hide
*/
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
index 3f13abe869da..3f13abe869da 100644
--- a/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
index fca084a00a79..fca084a00a79 100644
--- a/core/java/android/net/vcn/VcnNetworkPolicyResult.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
index 3638429f33fb..3638429f33fb 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
index 6cb6ee685a64..6cb6ee685a64 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
index 2b5305d05dcd..2b5305d05dcd 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
index e1d1b3c65c99..e1d1b3c65c99 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index 16114dd135af..16114dd135af 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index c7b2f188ba96..770a8c118a4d 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -17,22 +17,22 @@ package android.net.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.net.NetworkCapabilities;
import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
index 35b318687773..35b318687773 100644
--- a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
index ce5ec75f01a2..48c1b25a97ab 100644
--- a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
@@ -20,10 +20,10 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.net.ipsec.ike.ChildSaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.List;
import java.util.Objects;
diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
index 853a52da766a..dc1ee36b71c1 100644
--- a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
@@ -28,10 +28,10 @@ import android.net.eap.EapSessionConfig.EapMsChapV2Config;
import android.net.eap.EapSessionConfig.EapSimConfig;
import android.net.eap.EapSessionConfig.EapTtlsConfig;
import android.net.eap.EapSessionConfig.EapUiccConfig;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
index 6acb34ebb78e..6e8616fc9cb0 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
@@ -27,10 +27,10 @@ import android.net.ipsec.ike.IkeIpv4AddrIdentification;
import android.net.ipsec.ike.IkeIpv6AddrIdentification;
import android.net.ipsec.ike.IkeKeyIdIdentification;
import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
index 1459671f4136..b590148de51f 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
@@ -20,10 +20,10 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.net.ipsec.ike.IkeSaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.List;
import java.util.Objects;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index d1531a119a0d..aefac2e89aea 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -35,12 +35,12 @@ import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.net.InetAddress;
import java.security.PrivateKey;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
index 6bbc6b1e8218..6bbc6b1e8218 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
index 0c9ee8432798..469966a48465 100644
--- a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
@@ -18,11 +18,10 @@ package android.net.vcn.persistablebundleutils;
import android.annotation.NonNull;
import android.net.ipsec.ike.SaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.util.Pair;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
index 0427742f9c0a..0427742f9c0a 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
index e62acac14bd7..3f4ba345a118 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
@@ -34,11 +34,11 @@ import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netma
import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.PersistableBundle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
diff --git a/services/core/java/com/android/server/vcn/util/LogUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
index 93728ceb27c5..742aa761e602 100644
--- a/services/core/java/com/android/server/vcn/util/LogUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
import android.annotation.Nullable;
import android.os.ParcelUuid;
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
/** @hide */
public class LogUtils {
diff --git a/services/core/java/com/android/server/vcn/util/MtuUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
index 356c71f5f26a..c3123bcecf33 100644
--- a/services/core/java/com/android/server/vcn/util/MtuUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
diff --git a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java b/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
index e79bb2d2547d..a7ef67b187b9 100644
--- a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
/**
* OneWayBoolean is an abstraction for a boolean that MUST only ever be flipped from false to true
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
index d6761a2b37d8..8a687d8c5942 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 03ef4e67af1c..c1a1ee7875d0 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -32,6 +32,33 @@ filegroup {
visibility: ["//frameworks/base/services/core"],
}
+// Do not static include this lib in VCN because these files exist in
+// both service-connectivity.jar and framework.jar
+// TODO: b/374174952 After VCN moves to Connectivity/ and the modularization is done
+// this lib can be removed and "service-connectivity-b-pre-jarjar" can include
+// "service-connectivity-pre-jarjar"
+java_library {
+ name: "connectivity-utils-service-vcn-internal",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ srcs: [
+ ":framework-connectivity-shared-srcs",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "unsupportedappusage",
+ ],
+ visibility: [
+ "//visibility:private",
+ ],
+ apex_available: [
+ // TODO: b/374174952 Remove it when VCN modularization is released
+ "//apex_available:platform",
+
+ "com.android.tethering",
+ ],
+}
+
java_library {
name: "service-connectivity-b-pre-jarjar",
sdk_version: "system_server_current",
@@ -42,8 +69,33 @@ java_library {
"src/**/*.java",
],
- // TODO: b/375213246 Expose this library to Tethering module
+ libs: [
+ "android.net.ipsec.ike.stubs.module_lib",
+ "connectivity-utils-service-vcn-internal",
+ "framework-annotations-lib",
+ "framework-connectivity-pre-jarjar",
+ "framework-connectivity-t-pre-jarjar",
+ "framework-connectivity-b-pre-jarjar",
+ "framework-wifi.stubs.module_lib",
+ "keepanno-annotations",
+ "modules-utils-statemachine",
+ "unsupportedappusage",
+ ],
+
+ // TODO: b/374174952 Dynamically include these libs when VCN
+ // modularization is released
+ static_libs: [
+ "net-utils-service-vcn",
+ "modules-utils-handlerexecutor",
+ ],
+
visibility: [
"//frameworks/base/services",
],
+ apex_available: [
+ // TODO: b/374174952 Remove it when VCN modularization is released
+ "//apex_available:platform",
+
+ "com.android.tethering",
+ ],
}
diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
new file mode 100644
index 000000000000..02c8ce4f29e9
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+
+/**
+ * Service initializer for VCN. This is called by system server to create a new instance of
+ * VcnManagementService.
+ */
+// This class is reflectively invoked from SystemServer and ConnectivityServiceInitializer.
+// Without this annotation, this class will be treated as unused class and be removed during build
+// time.
+@UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+public final class ConnectivityServiceInitializerB extends SystemService {
+ private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName();
+ private final VcnManagementService mVcnManagementService;
+
+ public ConnectivityServiceInitializerB(Context context) {
+ super(context);
+ mVcnManagementService = VcnManagementService.create(context);
+ }
+
+ @Override
+ public void onStart() {
+ if (mVcnManagementService != null) {
+ Log.i(TAG, "Registering " + Context.VCN_MANAGEMENT_SERVICE);
+ publishBinderService(
+ Context.VCN_MANAGEMENT_SERVICE,
+ mVcnManagementService,
+ /* allowIsolated= */ false);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (mVcnManagementService != null && phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ Log.i(TAG, "Starting " + Context.VCN_MANAGEMENT_SERVICE);
+ mVcnManagementService.systemReady();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
index 2012f5632a64..26db6a988e31 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
@@ -55,6 +55,8 @@ import android.net.vcn.VcnConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.net.vcn.VcnManager.VcnStatusCode;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.net.wifi.WifiInfo;
import android.os.Binder;
import android.os.Build;
@@ -84,14 +86,13 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.PermissionUtils;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
-import com.android.server.vcn.util.PersistableBundleUtils;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import java.io.File;
import java.io.FileDescriptor;
@@ -1332,41 +1333,46 @@ public class VcnManagementService extends IVcnManagementService.Stub {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "| ");
// Post to handler thread to prevent ConcurrentModificationExceptions, and avoid lock-hell.
- mHandler.runWithScissors(() -> {
- mNetworkProvider.dump(pw);
- pw.println();
+ HandlerUtils.runWithScissorsForDump(
+ mHandler,
+ () -> {
+ mNetworkProvider.dump(pw);
+ pw.println();
- mTrackingNetworkCallback.dump(pw);
- pw.println();
+ mTrackingNetworkCallback.dump(pw);
+ pw.println();
- synchronized (mLock) {
- mLastSnapshot.dump(pw);
- pw.println();
+ synchronized (mLock) {
+ mLastSnapshot.dump(pw);
+ pw.println();
- pw.println("mConfigs:");
- pw.increaseIndent();
- for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
- pw.println(entry.getKey() + ": "
- + entry.getValue().getProvisioningPackageName());
- }
- pw.decreaseIndent();
- pw.println();
+ pw.println("mConfigs:");
+ pw.increaseIndent();
+ for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+ pw.println(
+ entry.getKey()
+ + ": "
+ + entry.getValue().getProvisioningPackageName());
+ }
+ pw.decreaseIndent();
+ pw.println();
- pw.println("mVcns:");
- pw.increaseIndent();
- for (Vcn vcn : mVcns.values()) {
- vcn.dump(pw);
- }
- pw.decreaseIndent();
- pw.println();
- }
+ pw.println("mVcns:");
+ pw.increaseIndent();
+ for (Vcn vcn : mVcns.values()) {
+ vcn.dump(pw);
+ }
+ pw.decreaseIndent();
+ pw.println();
+ }
- pw.println("Local log:");
- pw.increaseIndent();
- LOCAL_LOG.dump(pw);
- pw.decreaseIndent();
- pw.println();
- }, DUMP_TIMEOUT_MILLIS);
+ pw.println("Local log:");
+ pw.increaseIndent();
+ LOCAL_LOG.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
+ },
+ DUMP_TIMEOUT_MILLIS);
}
// TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
index 3392d039109a..b448f7595b3b 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -27,8 +27,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.vcn.VcnManager;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
@@ -45,7 +45,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import com.android.modules.utils.HandlerExecutor;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 1fba29779f0f..2524d0eedac3 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -38,8 +38,8 @@ import android.net.Uri;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
+import android.net.vcn.util.LogUtils;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
@@ -52,9 +52,9 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.util.LogUtils;
import java.util.Arrays;
import java.util.Collections;
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
index 9213d96ad4ca..9213d96ad4ca 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
index 2325f358e301..e50fc3a6e8b9 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
@@ -30,10 +30,10 @@ import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENAB
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.VcnManagementService.VDBG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -45,6 +45,7 @@ import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpSecManager;
import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.PolicyDirection;
import android.net.IpSecManager.ResourceUnavailableException;
import android.net.IpSecTransform;
import android.net.LinkAddress;
@@ -59,7 +60,6 @@ import android.net.NetworkScore;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
-import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSaProposal;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -78,9 +78,11 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.LogUtils;
+import android.net.vcn.util.MtuUtils;
+import android.net.vcn.util.OneWayBoolean;
import android.net.wifi.WifiInfo;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
@@ -98,14 +100,12 @@ import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.HandlerExecutor;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
-import com.android.server.vcn.util.LogUtils;
-import com.android.server.vcn.util.MtuUtils;
-import com.android.server.vcn.util.OneWayBoolean;
import java.io.IOException;
import java.net.Inet4Address;
@@ -1228,10 +1228,7 @@ public class VcnGatewayConnection extends StateMachine {
@VisibleForTesting(visibility = Visibility.PRIVATE)
void setSafeModeAlarm() {
- final boolean isFlagSafeModeConfigEnabled = mVcnContext.getFeatureFlags().safeModeConfig();
- logVdbg("isFlagSafeModeConfigEnabled " + isFlagSafeModeConfigEnabled);
-
- if (isFlagSafeModeConfigEnabled && !mConnectionConfig.isSafeModeEnabled()) {
+ if (!mConnectionConfig.isSafeModeEnabled()) {
logVdbg("setSafeModeAlarm: safe mode disabled");
return;
}
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 78ff432f5423..4552f509b59a 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -33,7 +33,6 @@ import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -41,6 +40,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
import java.util.Objects;
import java.util.Set;
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 16ab51e8d604..72de61363d26 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,9 @@
package com.android.server.vcn.routeselection;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -31,12 +32,12 @@ import android.net.IpSecTransformState;
import android.net.Network;
import android.net.vcn.VcnManager;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.OutcomeReceiver;
import android.os.PowerManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
import com.android.server.vcn.VcnContext;
import java.lang.annotation.ElementType;
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 0d4c3736775b..86cee554be6f 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -16,8 +16,9 @@
package com.android.server.vcn.routeselection;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index d32e5cc8ef80..79c4116d0cd4 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -23,9 +23,9 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 3eeeece5da46..29a0762f5fe8 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -19,12 +19,12 @@ package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
import static com.android.server.VcnManagementService.LOCAL_LOG;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,8 +39,8 @@ import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.net.vcn.util.LogUtils;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
@@ -51,10 +51,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
-import com.android.server.vcn.util.LogUtils;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 08be11e29689..30f4ed1b9f0b 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -16,8 +16,9 @@
package com.android.server.vcn.routeselection;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index 1945052b92df..1945052b92df 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index ca4b5d4170c7..6ccfaf3e0b66 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -41,6 +41,7 @@
<activity android:name=".PlatformVpnConfirmDialog"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight"
android:noHistory="true"
+ android:enableOnBackInvokedCallback="false"
android:excludeFromRecents="true"
android:exported="true">
</activity>
diff --git a/proto/Android.bp b/proto/Android.bp
index a5e13350ebd2..feaa6d2e9b73 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -25,6 +25,10 @@ java_library_static {
static_libs: ["libprotobuf-java-nano"],
},
},
+ apex_available: [
+ "com.android.neuralnetworks",
+ "//apex_available:platform",
+ ],
}
java_library_static {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 0c2ce8dcb698..59043a8356ae 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -182,21 +182,6 @@ java_device_for_host {
visibility: [":__subpackages__"],
}
-// Separated out from ravenwood-junit-impl since it needs to compile
-// against `module_current`
-java_library {
- name: "ravenwood-junit-impl-flag",
- srcs: [
- "junit-flag-src/**/*.java",
- ],
- sdk_version: "module_current",
- libs: [
- "junit",
- "flag-junit",
- ],
- visibility: ["//visibility:public"],
-}
-
// Carefully compiles against only module_current to support tests that
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
@@ -282,20 +267,12 @@ cc_defaults {
visibility: ["//visibility:private"],
}
-cc_library_host_shared {
- name: "libravenwood_initializer",
- defaults: ["ravenwood_jni_defaults"],
- srcs: [
- "runtime-jni/ravenwood_initializer.cpp",
- ],
-}
-
// We need this as a separate library because we need to overload the
// sysprop symbols before libbase is loaded into the process
cc_library_host_shared {
- name: "libravenwood_sysprop",
+ name: "libravenwood_initializer",
defaults: ["ravenwood_jni_defaults"],
- srcs: ["runtime-jni/ravenwood_sysprop.cpp"],
+ srcs: ["runtime-jni/ravenwood_initializer.cpp"],
}
cc_library_host_shared {
@@ -659,7 +636,6 @@ android_ravenwood_libgroup {
"flag-junit",
"ravenwood-framework",
"ravenwood-junit-impl",
- "ravenwood-junit-impl-flag",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
@@ -669,7 +645,6 @@ android_ravenwood_libgroup {
jni_libs: [
// Libraries has to be loaded in the following order
"libravenwood_initializer",
- "libravenwood_sysprop",
"libravenwood_runtime",
"libandroid_runtime",
],
diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
deleted file mode 100644
index 9d6277473298..000000000000
--- a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
+++ /dev/null
@@ -1,54 +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.platform.test.flag.junit;
-
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.IFlagsValueProvider;
-
-/**
- * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless
- * testing environment.
- *
- * At the moment, default flag values are not available on Ravenwood, so the only options offered
- * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should
- * use {@link android.platform.test.flag.junit.SetFlagsRule}.
- */
-public class RavenwoodFlagsValueProvider {
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state.
- */
- public static CheckFlagsRule createAllOnCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return true;
- }
- });
- }
-
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state.
- */
- public static CheckFlagsRule createAllOffCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return false;
- }
- });
- }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index de3c5f2c23ab..3ebef02284d6 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -63,8 +63,6 @@ import java.util.function.BiConsumer;
* - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
*/
public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
- public static final String TAG = "Ravenwood";
-
/** Scope of a hook. */
public enum Scope {
Class,
@@ -131,7 +129,7 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
mTestClass = new TestClass(testClass);
- Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
+ Log.v(TAG, "RavenwoodAwareTestRunner initializing for " + testClass.getCanonicalName());
// Hook point to allow more customization.
runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
@@ -148,7 +146,9 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
Object instance) {
- Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+ }
for (var method : mTestClass.getAnnotatedMethods(annotationClass)) {
ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
@@ -171,12 +171,14 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
if (mRealRunner instanceof ClassSkippingTestRunner) {
- Log.i(TAG, "onClassSkipped: description=" + description);
+ Log.v(TAG, "onClassSkipped: description=" + description);
mRealRunner.run(notifier);
return;
}
- Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "Running " + mTestJavaClass.getCanonicalName());
+ }
if (RAVENWOOD_VERBOSE_LOGGING) {
dumpDescription(description);
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 239c8061b757..a3326337d485 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -45,7 +45,7 @@ import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class RavenwoodContext extends RavenwoodBaseContext {
- private static final String TAG = "Ravenwood";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private final Object mLock = new Object();
private final String mPackageName;
@@ -132,36 +132,27 @@ public class RavenwoodContext extends RavenwoodBaseContext {
@Override
public Looper getMainLooper() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getLooper();
}
@Override
public Handler getMainThreadHandler() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getThreadHandler();
}
@Override
public Executor getMainExecutor() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getThreadExecutor();
}
@Override
public String getPackageName() {
- return Objects.requireNonNull(mPackageName,
- "Test must request setPackageName() (or setTargetPackageName())"
- + " via RavenwoodConfig");
+ return mPackageName;
}
@Override
public String getOpPackageName() {
- return Objects.requireNonNull(mPackageName,
- "Test must request setPackageName() via RavenwoodConfig");
+ return mPackageName;
}
@Override
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
index 77275c445dd9..3cb2c67adf09 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
@@ -27,8 +27,6 @@ import org.junit.runner.Description;
* Calculates which tests need to be executed on Ravenwood.
*/
public class RavenwoodEnablementChecker {
- private static final String TAG = "RavenwoodDisablementChecker";
-
private RavenwoodEnablementChecker() {
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 6dfcf4ce03cf..70bc52bdaa12 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -15,6 +15,8 @@
*/
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -40,7 +42,7 @@ import java.util.Set;
* All members must be called from the runner's main thread.
*/
public final class RavenwoodRunnerState {
- private static final String TAG = "RavenwoodRunnerState";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private static final String RAVENWOOD_RULE_ERROR =
"RavenwoodRule(s) are not executed in the correct order";
@@ -59,16 +61,22 @@ public final class RavenwoodRunnerState {
private Description mMethodDescription;
public void enterTestRunner() {
- Log.i(TAG, "enterTestRunner: " + mRunner);
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "enterTestRunner: " + mRunner);
+ }
RavenwoodRuntimeEnvironmentController.initForRunner();
}
public void enterTestClass() {
- Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
+ }
}
public void exitTestClass() {
- Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
+ }
assertTrue(RAVENWOOD_RULE_ERROR, sActiveProperties.isEmpty());
RavenwoodRuntimeEnvironmentController.exitTestClass();
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index e730a292a9da..3cb6c5a6bd16 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -58,6 +58,7 @@ import android.provider.DeviceConfig_host;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import android.util.Log_ravenwood;
import android.view.DisplayAdjustments;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -95,16 +96,18 @@ import java.util.function.Supplier;
* Responsible for initializing and the environment.
*/
public class RavenwoodRuntimeEnvironmentController {
- private static final String TAG = "RavenwoodRuntimeEnvironmentController";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private RavenwoodRuntimeEnvironmentController() {
}
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
- private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
+ private static final String ANDROID_LOG_TAGS = "ANDROID_LOG_TAGS";
+ private static final String RAVENWOOD_ANDROID_LOG_TAGS = "RAVENWOOD_" + ANDROID_LOG_TAGS;
+
/**
* When enabled, attempt to dump all thread stacks just before we hit the
* overall Tradefed timeout, to aid in debugging deadlocks.
@@ -214,42 +217,45 @@ public class RavenwoodRuntimeEnvironmentController {
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
- // Some process-wide initialization. (maybe redirect stdout/stderr)
- RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
+ // Some process-wide initialization:
+ // - maybe redirect stdout/stderr
+ // - override native system property functions
+ var lib = RavenwoodCommonUtils.getJniLibraryPath(LIBRAVENWOOD_INITIALIZER_NAME);
+ System.load(lib);
+ RavenwoodRuntimeNative.reloadNativeLibrary(lib);
+
+ // Redirect stdout/stdin to the Log API.
+ RuntimeInit.redirectLogStreams();
dumpCommandLineArgs();
// We haven't initialized liblog yet, so directly write to System.out here.
RavenwoodCommonUtils.log(TAG, "globalInitInner()");
- // Load libravenwood_sysprop before other libraries that may use SystemProperties.
- var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME);
- System.load(libProp);
- RavenwoodRuntimeNative.reloadNativeLibrary(libProp);
-
// Make sure libravenwood_runtime is loaded.
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
+ Log_ravenwood.setLogLevels(getLogTags());
+ Log_ravenwood.onRavenwoodRuntimeNativeReady();
+
// Do the basic set up for the android sysprops.
RavenwoodSystemProperties.initialize();
+ // Enable all log levels for native logging, until we'll have a way to change the native
+ // side log level at runtime.
// Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
// before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
- if (RAVENWOOD_VERBOSE_LOGGING) {
- RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
- try {
- Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
- } catch (ErrnoException e) {
- throw new RuntimeException(e);
- }
+ // This would also prevent libbase from crashing the process (b/381112373) because
+ // the string format it accepts is very limited.
+ try {
+ Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+ } catch (ErrnoException e) {
+ throw new RuntimeException(e);
}
// Make sure libandroid_runtime is loaded.
RavenwoodNativeLoader.loadFrameworkNativeCode();
- // Redirect stdout/stdin to liblog.
- RuntimeInit.redirectLogStreams();
-
// Touch some references early to ensure they're <clinit>'ed
Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
@@ -332,6 +338,18 @@ public class RavenwoodRuntimeEnvironmentController {
initializeCompatIds();
}
+ /**
+ * Get log tags from environmental variable.
+ */
+ @Nullable
+ private static String getLogTags() {
+ var logTags = System.getenv(RAVENWOOD_ANDROID_LOG_TAGS);
+ if (logTags == null) {
+ logTags = System.getenv(ANDROID_LOG_TAGS);
+ }
+ return logTags;
+ }
+
private static void loadRavenwoodProperties() {
var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
@@ -534,7 +552,7 @@ public class RavenwoodRuntimeEnvironmentController {
}
private static void dumpCommandLineArgs() {
- Log.i(TAG, "JVM arguments:");
+ Log.v(TAG, "JVM arguments:");
// Note, we use the wrapper in JUnit4, not the actual class (
// java.lang.management.ManagementFactory), because we can't see the later at the build
@@ -543,7 +561,7 @@ public class RavenwoodRuntimeEnvironmentController {
var args = ManagementFactory.getRuntimeMXBean().getInputArguments();
for (var arg : args) {
- Log.i(TAG, " " + arg);
+ Log.v(TAG, " " + arg);
}
}
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index c545baacdf3e..70c161c1f19a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -34,7 +34,7 @@ import java.util.Set;
* A class to manage the core default system properties of the Ravenwood environment.
*/
public class RavenwoodSystemProperties {
- private static final String TAG = "RavenwoodSystemProperties";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
/** We pull in properties from this file. */
private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
@@ -84,7 +84,7 @@ public class RavenwoodSystemProperties {
var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
- Log.i(TAG, "Default system properties:");
+ Log.v(TAG, "Default system properties:");
ravenwoodProps.forEach((key, origValue) -> {
final String value;
@@ -100,7 +100,7 @@ public class RavenwoodSystemProperties {
} else {
value = origValue;
}
- Log.i(TAG, key + "=" + value);
+ Log.v(TAG, key + "=" + value);
sDefaultValues.put(key, value);
});
@@ -132,9 +132,10 @@ public class RavenwoodSystemProperties {
}
private static boolean isKeyReadable(String key) {
- final String root = getKeyRoot(key);
+ // All writable keys are also readable
+ if (isKeyWritable(key)) return true;
- if (root.startsWith("debug.")) return true;
+ final String root = getKeyRoot(key);
// This set is carefully curated to help identify situations where a test may
// accidentally depend on a default value of an obscure property whose owner hasn't
@@ -145,26 +146,24 @@ public class RavenwoodSystemProperties {
if (root.startsWith("soc.")) return true;
if (root.startsWith("system.")) return true;
- // For PropertyInvalidatedCache
- if (root.startsWith("cache_key.")) return true;
-
- switch (key) {
- case "gsm.version.baseband":
- case "no.such.thing":
- case "qemu.sf.lcd_density":
- case "ro.bootloader":
- case "ro.debuggable":
- case "ro.hardware":
- case "ro.hw_timeout_multiplier":
- case "ro.odm.build.media_performance_class":
- case "ro.sf.lcd_density":
- case "ro.treble.enabled":
- case "ro.vndk.version":
- case "ro.icu.data.path":
- return true;
- }
-
- return false;
+ // All core values should be readable
+ if (sDefaultValues.containsKey(key)) return true;
+
+ // Hardcoded allowlist
+ return switch (key) {
+ case "gsm.version.baseband",
+ "no.such.thing",
+ "qemu.sf.lcd_density",
+ "ro.bootloader",
+ "ro.hardware",
+ "ro.hw_timeout_multiplier",
+ "ro.odm.build.media_performance_class",
+ "ro.sf.lcd_density",
+ "ro.treble.enabled",
+ "ro.vndk.version",
+ "ro.icu.data.path" -> true;
+ default -> false;
+ };
}
private static boolean isKeyWritable(String key) {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 787058545fed..c8b10d20843f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -41,7 +41,7 @@ import java.util.Map;
* `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_latest.csv`.
*/
public class RavenwoodTestStats {
- private static final String TAG = "RavenwoodTestStats";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
private static RavenwoodTestStats sInstance;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
index 31a14164bd51..359210582ba5 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
@@ -15,6 +15,8 @@
*/
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+
import android.platform.test.annotations.internal.InnerRunner;
import android.util.Log;
@@ -35,7 +37,7 @@ import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.TestClass;
abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable {
- private static final String TAG = "Ravenwood";
+ public static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
boolean mRealRunnerTakesRunnerBuilder = false;
@@ -53,7 +55,9 @@ abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable
}
try {
- Log.i(TAG, "Initializing the inner runner: " + runnerClass);
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ Log.v(TAG, "Initializing the inner runner: " + runnerClass);
+ }
try {
return runnerClass.getConstructor(Class.class)
.newInstance(testClass.getJavaClass());
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
deleted file mode 100644
index 3ed0f50434fb..000000000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ /dev/null
@@ -1,139 +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.platform.test.ravenwood;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
- */
-@Deprecated
-public final class RavenwoodConfig {
- /**
- * Use this to mark a field as the configuration.
- * @hide
- */
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Config {
- }
-
- /**
- * Stores internal states / methods associated with this config that's only needed in
- * junit-impl.
- */
- private RavenwoodConfig() {
- }
-
- /**
- * Return if the current process is running on a Ravenwood test environment.
- */
- public static boolean isOnRavenwood() {
- return RavenwoodRule.isOnRavenwood();
- }
-
- public static class Builder {
- private final RavenwoodConfig mConfig = new RavenwoodConfig();
-
- public Builder() {
- }
-
- /**
- * @deprecated no longer used. We always use an app UID.
- */
- @Deprecated
- public Builder setProcessSystem() {
- return this;
- }
-
- /**
- * @deprecated no longer used. We always use an app UID.
- */
- @Deprecated
- public Builder setProcessApp() {
- return this;
- }
-
- /**
- * @deprecated no longer used. Package name is set in the build file. (for now)
- */
- @Deprecated
- public Builder setPackageName(@NonNull String packageName) {
- return this;
- }
-
- /**
- * @deprecated no longer used. Package name is set in the build file. (for now)
- */
- @Deprecated
- public Builder setTargetPackageName(@NonNull String packageName) {
- return this;
- }
-
-
- /**
- * @deprecated no longer used. Target SDK level is set in the build file. (for now)
- */
- @Deprecated
- public Builder setTargetSdkLevel(int sdkLevel) {
- return this;
- }
-
- /**
- * @deprecated no longer used. Main thread is always available.
- */
- @Deprecated
- public Builder setProvideMainThread(boolean provideMainThread) {
- return this;
- }
-
- /**
- * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyImmutable(String, Object)}
- */
- @Deprecated
- public Builder setSystemPropertyImmutable(@NonNull String key,
- @Nullable Object value) {
- return this;
- }
-
- /**
- * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyMutable(String, Object)}
- */
- @Deprecated
- public Builder setSystemPropertyMutable(@NonNull String key,
- @Nullable Object value) {
- return this;
- }
-
- /**
- * @deprecated no longer used. All supported services are available.
- */
- @Deprecated
- public Builder setServicesRequired(@NonNull Class<?>... services) {
- return this;
- }
-
- public RavenwoodConfig build() {
- return mConfig;
- }
- }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index e49d3d934e9f..ffe5f6c8c579 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -36,12 +36,10 @@ import java.util.Objects;
import java.util.regex.Pattern;
/**
- * @deprecated This class is undergoing a major change. Reach out to g/ravenwood if you need
- * any featues in it.
+ * Reach out to g/ravenwood if you need any features in it.
*/
-@Deprecated
public final class RavenwoodRule implements TestRule {
- private static final String TAG = "RavenwoodRule";
+ private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
@@ -193,6 +191,12 @@ public final class RavenwoodRule implements TestRule {
return IS_ON_RAVENWOOD;
}
+ private static void ensureOnRavenwood(String featureName) {
+ if (!IS_ON_RAVENWOOD) {
+ throw new RuntimeException(featureName + " is only supported on Ravenwood.");
+ }
+ }
+
/**
* @deprecated Use
* {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()}
@@ -242,6 +246,40 @@ public final class RavenwoodRule implements TestRule {
return System.currentTimeMillis();
}
+ /**
+ * Equivalent to setting the ANDROID_LOG_TAGS environmental variable.
+ *
+ * See https://developer.android.com/tools/logcat#filteringOutput for the string format.
+ *
+ * NOTE: this works only on Ravenwood.
+ */
+ public static void setAndroidLogTags(@Nullable String androidLogTags) {
+ ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()");
+ try {
+ Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+ var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class);
+ setter.invoke(null, androidLogTags);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level.
+ *
+ * NOTE: this works only on Ravenwood.
+ */
+ public static void setLogLevel(@Nullable String tag, int level) {
+ ensureOnRavenwood("RavenwoodRule.setLogLevel()");
+ try {
+ Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+ var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class);
+ setter.invoke(null, tag, level);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
// Below are internal to ravenwood. Don't use them from normal tests...
public static class RavenwoodPrivate {
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index b4b751788c6e..8d9bcd5b242f 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -31,8 +31,6 @@ import org.junit.runners.model.TestClass;
* This is only used when a real device-side test has Ravenizer enabled.
*/
public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
- private static final String TAG = "Ravenwood";
-
private static class NopRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 2a04d4469ef4..a967a3fff0d7 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -33,7 +33,7 @@ import java.util.Arrays;
import java.util.function.Supplier;
public class RavenwoodCommonUtils {
- private static final String TAG = "RavenwoodCommonUtils";
+ public static final String TAG = "Ravenwood";
private RavenwoodCommonUtils() {
}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
deleted file mode 100644
index c85bd23db893..000000000000
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
+++ /dev/null
@@ -1,84 +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 android.util;
-
-import android.util.Log.Level;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
-import java.io.PrintStream;
-
-/**
- * Ravenwood "native substitution" class for {@link android.util.Log}.
- *
- * {@link android.util.Log} already uses the actual native code and doesn't use this class.
- * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass
- * annotation on {@link android.util.Log}.
- */
-public class Log_host {
-
- public static boolean isLoggable(String tag, @Level int level) {
- return true;
- }
-
- public static int println_native(int bufID, int priority, String tag, String msg) {
- if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) {
- return msg.length(); // No verbose logging.
- }
- final String buffer;
- switch (bufID) {
- case Log.LOG_ID_MAIN: buffer = "main"; break;
- case Log.LOG_ID_RADIO: buffer = "radio"; break;
- case Log.LOG_ID_EVENTS: buffer = "event"; break;
- case Log.LOG_ID_SYSTEM: buffer = "system"; break;
- case Log.LOG_ID_CRASH: buffer = "crash"; break;
- default: buffer = "buf:" + bufID; break;
- }
-
- final String prio;
- switch (priority) {
- case Log.VERBOSE: prio = "V"; break;
- case Log.DEBUG: prio = "D"; break;
- case Log.INFO: prio = "I"; break;
- case Log.WARN: prio = "W"; break;
- case Log.ERROR: prio = "E"; break;
- case Log.ASSERT: prio = "A"; break;
- default: prio = "prio:" + priority; break;
- }
-
- for (String s : msg.split("\\n")) {
- getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
- }
- return msg.length();
- }
-
- public static int logger_entry_max_payload_native() {
- return 4068; // [ravenwood] This is what people use in various places.
- }
-
- /**
- * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
- * that we don't end up in a recursive loop.
- */
- private static PrintStream getRealOut() {
- if (RuntimeInit.sOut$ravenwood != null) {
- return RuntimeInit.sOut$ravenwood;
- } else {
- return System.out;
- }
- }
-}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
new file mode 100644
index 000000000000..7ab9cda378b7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.annotation.Nullable;
+import android.util.Log.Level;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Ravenwood "native substitution" class for {@link android.util.Log}.
+ *
+ * {@link android.util.Log} already uses the actual native code and doesn't use this class.
+ * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass
+ * annotation on {@link android.util.Log}.
+ */
+public class Log_ravenwood {
+
+ private static final SimpleDateFormat sTimestampFormat =
+ new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static int sDefaultLogLevel;
+
+ @GuardedBy("sLock")
+ private static final Map<String, Integer> sTagLogLevels = new HashMap<>();
+
+ /**
+ * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setAndroidLogTags(String)}
+ * via reflections.
+ */
+ public static void setLogLevels(String androidLogTags) {
+ var map = parseLogLevels(androidLogTags);
+
+ synchronized (sLock) {
+ sTagLogLevels.clear();
+ sTagLogLevels.putAll(map);
+
+ var def = map.get("*");
+ sDefaultLogLevel = def != null ? def : Log.VERBOSE;
+ }
+ }
+
+ private static Map<String, Integer> parseLogLevels(String androidLogTags) {
+ final Map<String, Integer> ret = new HashMap<>();
+
+ if (androidLogTags == null) {
+ return ret;
+ }
+
+ String[] tagPairs = androidLogTags.trim().split("\\s+");
+ for (String tagPair : tagPairs) {
+ String[] parts = tagPair.split(":");
+ if (parts.length == 2) {
+ String tag = parts[0];
+ try {
+ int priority = switch (parts[1].toUpperCase(Locale.ROOT)) {
+ case "V": yield Log.VERBOSE;
+ case "D": yield Log.DEBUG;
+ case "I": yield Log.INFO;
+ case "W": yield Log.WARN;
+ case "E": yield Log.ERROR;
+ case "F": yield Log.ERROR + 1; // Not used in the java side.
+ case "S": yield Integer.MAX_VALUE; // Silent
+ default: throw new IllegalArgumentException(
+ "Invalid priority level for tag: " + tag);
+ };
+
+ ret.put(tag, priority);
+ } catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage());
+ }
+ } else {
+ System.err.println("Invalid tag format: " + tagPair);
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setLogLevel(String, int)}
+ * via reflections. Pass NULL to {@code tag} to set the default level.
+ */
+ public static void setLogLevel(@Nullable String tag, int level) {
+ synchronized (sLock) {
+ if (tag == null) {
+ sDefaultLogLevel = level;
+ } else {
+ sTagLogLevels.put(tag, level);
+ }
+ }
+ }
+
+ /**
+ * Replaces {@link Log#isLoggable}.
+ */
+ public static boolean isLoggable(String tag, @Level int priority) {
+ synchronized (sLock) {
+ var threshold = sTagLogLevels.get(tag);
+ if (threshold == null) {
+ threshold = sDefaultLogLevel;
+ }
+ return priority >= threshold;
+ }
+ }
+
+ public static int println_native(int bufID, int priority, String tag, String msg) {
+ if (!isLoggable(tag, priority)) {
+ return msg.length();
+ }
+
+ final String prio;
+ switch (priority) {
+ case Log.VERBOSE: prio = "V"; break;
+ case Log.DEBUG: prio = "D"; break;
+ case Log.INFO: prio = "I"; break;
+ case Log.WARN: prio = "W"; break;
+ case Log.ERROR: prio = "E"; break;
+ case Log.ASSERT: prio = "A"; break;
+ default: prio = "prio:" + priority; break;
+ }
+
+ String leading = sTimestampFormat.format(new Date())
+ + " %-6d %-6d %s %-8s: ".formatted(getPid(), getTid(), prio, tag);
+ var out = getRealOut();
+ for (String s : msg.split("\\n")) {
+ out.print(leading);
+ out.println(s);
+ }
+ return msg.length();
+ }
+
+ public static int logger_entry_max_payload_native() {
+ return 4068; // [ravenwood] This is what people use in various places.
+ }
+
+ /**
+ * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+ * that we don't end up in a recursive loop.
+ */
+ private static PrintStream getRealOut() {
+ if (RuntimeInit.sOut$ravenwood != null) {
+ return RuntimeInit.sOut$ravenwood;
+ } else {
+ return System.out;
+ }
+ }
+
+ /**
+ * PID. We need to use a JNI method to get it, but JNI isn't initially ready.
+ * Call {@link #onRavenwoodRuntimeNativeReady} to signal when JNI is ready, at which point
+ * we set this field.
+ * (We don't want to call native methods that may not be fully initialized even with a
+ * try-catch, because partially initialized JNI methods could crash the process.)
+ */
+ private static volatile int sPid = 0;
+
+ private static ThreadLocal<Integer> sTid =
+ ThreadLocal.withInitial(RavenwoodRuntimeNative::gettid);
+
+ /**
+ * Call it when {@link RavenwoodRuntimeNative} is usable.
+ */
+ public static void onRavenwoodRuntimeNativeReady() {
+ sPid = RavenwoodRuntimeNative.getpid();
+ }
+
+ private static int getPid() {
+ return sPid;
+ }
+
+ private static int getTid() {
+ if (sPid == 0) {
+ return 0; // Native methods not ready yet.
+ }
+ return sTid.get();
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index 9a78989dad55..acbcdf1926db 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -62,6 +62,8 @@ public class RavenwoodRuntimeNative {
removeSystemProperty(null);
}
+ public static native int getpid();
+
public static native int gettid();
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
index 89fb7c3c3510..391c5d56b212 100644
--- a/ravenwood/runtime-jni/ravenwood_initializer.cpp
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -14,16 +14,174 @@
* limitations under the License.
*/
- /*
- * This file is compiled into a single SO file, which we load at the very first.
- * We can do process-wide initialization here.
- */
+/*
+ * This file is compiled into a single SO file, which we load at the very first.
+ * We can do process-wide initialization here.
+ * Please be aware that all symbols defined in this SO file will be reloaded
+ * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY
+ * want to expose and override globally.
+ */
+#include <dlfcn.h>
#include <fcntl.h>
-#include <unistd.h>
+
+#include <set>
#include "jni_helper.h"
+// Implement a rudimentary system properties data store
+
+#define PROP_VALUE_MAX 92
+
+namespace {
+
+struct prop_info {
+ std::string key;
+ mutable std::string value;
+ mutable uint32_t serial;
+
+ prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
+};
+
+struct prop_info_cmp {
+ using is_transparent = void;
+ bool operator()(const prop_info& lhs, const prop_info& rhs) {
+ return lhs.key < rhs.key;
+ }
+ bool operator()(std::string_view lhs, const prop_info& rhs) {
+ return lhs < rhs.key;
+ }
+ bool operator()(const prop_info& lhs, std::string_view rhs) {
+ return lhs.key < rhs;
+ }
+};
+
+} // namespace
+
+static auto& g_properties_lock = *new std::mutex;
+static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
+
+static bool property_set(const char* key, const char* value) {
+ if (key == nullptr || *key == '\0') return false;
+ if (value == nullptr) value = "";
+ bool read_only = !strncmp(key, "ro.", 3);
+ if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
+
+ std::lock_guard lock(g_properties_lock);
+ auto [it, success] = g_properties.emplace(key, value);
+ if (read_only) return success;
+ if (!success) {
+ it->value = value;
+ ++it->serial;
+ }
+ return true;
+}
+
+template <typename Func>
+static void property_get(const char* key, Func callback) {
+ std::lock_guard lock(g_properties_lock);
+ auto it = g_properties.find(key);
+ if (it != g_properties.end()) {
+ callback(*it);
+ }
+}
+
+// Redefine the __system_property_XXX functions here so we can perform
+// logging and access checks for all sysprops in native code.
+
+static void check_system_property_access(const char* key, bool write);
+
+extern "C" {
+
+int __system_property_set(const char* key, const char* value) {
+ check_system_property_access(key, true);
+ return property_set(key, value) ? 0 : -1;
+}
+
+int __system_property_get(const char* key, char* value) {
+ check_system_property_access(key, false);
+ *value = '\0';
+ property_get(key, [&](const prop_info& info) {
+ snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
+ });
+ return strlen(value);
+}
+
+const prop_info* __system_property_find(const char* key) {
+ check_system_property_access(key, false);
+ const prop_info* pi = nullptr;
+ property_get(key, [&](const prop_info& info) { pi = &info; });
+ return pi;
+}
+
+void __system_property_read_callback(const prop_info* pi,
+ void (*callback)(void*, const char*, const char*, uint32_t),
+ void* cookie) {
+ std::lock_guard lock(g_properties_lock);
+ callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
+}
+
+} // extern "C"
+
+// ---- JNI ----
+
+static JavaVM* gVM = nullptr;
+static jclass gRunnerState = nullptr;
+static jmethodID gCheckSystemPropertyAccess;
+
+static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
+ ScopedUtfChars path(env, javaPath);
+ // Force reload ourselves as global
+ dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
+}
+
+// Call back into Java code to check property access
+static void check_system_property_access(const char* key, bool write) {
+ if (gVM != nullptr && gRunnerState != nullptr) {
+ JNIEnv* env;
+ if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
+ ALOGV("%s access to system property '%s'", write ? "Write" : "Read", key);
+ env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
+ env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
+ return;
+ }
+ }
+ // Not on JVM thread, abort
+ LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
+}
+
+static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
+ ScopedUtfChars key(env, javaKey);
+ jstring value = nullptr;
+ property_get(key.c_str(),
+ [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
+ return value;
+}
+
+static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
+ ScopedUtfChars key(env, javaKey);
+ ScopedUtfChars value(env, javaValue);
+ return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
+ std::lock_guard lock(g_properties_lock);
+
+ if (javaKey == nullptr) {
+ g_properties.clear();
+ return JNI_TRUE;
+ } else {
+ ScopedUtfChars key(env, javaKey);
+ auto it = g_properties.find(key);
+ if (it != g_properties.end()) {
+ g_properties.erase(it);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+ }
+}
+
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
if (ravenwoodLogOut == NULL) {
@@ -42,9 +200,30 @@ static void maybeRedirectLog() {
dup2(ttyFd, 2);
}
+static const JNINativeMethod sMethods[] = {
+ {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
+ {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
+ {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
+ {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
+};
+
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- ALOGI("%s: JNI_OnLoad", __FILE__);
+ ALOGV("%s: JNI_OnLoad", __FILE__);
maybeRedirectLog();
+
+ JNIEnv* env = GetJNIEnvOrDie(vm);
+ gVM = vm;
+
+ // Fetch several references for future use
+ gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
+ gCheckSystemPropertyAccess =
+ GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
+ "(Ljava/lang/String;Z)V");
+
+ // Expose raw property methods as JNI methods
+ jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
+ if (res < 0) return -1;
+
return JNI_VERSION_1_4;
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index c1993f691686..8d8ed7119e84 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -174,6 +174,9 @@ static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaVal
throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
}
+static jint Linux_getpid(JNIEnv* env, jobject) {
+ return getpid();
+}
static jint Linux_gettid(JNIEnv* env, jobject) {
// gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
@@ -196,11 +199,12 @@ static const JNINativeMethod sMethods[] =
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
{ "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
{ "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+ { "getpid", "()I", (void*)Linux_getpid },
{ "gettid", "()I", (void*)Linux_gettid },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- ALOGI("%s: JNI_OnLoad", __FILE__);
+ ALOGV("%s: JNI_OnLoad", __FILE__);
JNIEnv* env = GetJNIEnvOrDie(vm);
g_StructStat = FindGlobalClassOrDie(env, "android/system/StructStat");
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
deleted file mode 100644
index a78aa8da9052..000000000000
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ /dev/null
@@ -1,200 +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.
- */
-
-#include <dlfcn.h>
-
-#include <set>
-
-#include "jni_helper.h"
-
-// Implement a rudimentary system properties data store
-
-#define PROP_VALUE_MAX 92
-
-namespace {
-
-struct prop_info {
- std::string key;
- mutable std::string value;
- mutable uint32_t serial;
-
- prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
-};
-
-struct prop_info_cmp {
- using is_transparent = void;
- bool operator()(const prop_info& lhs, const prop_info& rhs) {
- return lhs.key < rhs.key;
- }
- bool operator()(std::string_view lhs, const prop_info& rhs) {
- return lhs < rhs.key;
- }
- bool operator()(const prop_info& lhs, std::string_view rhs) {
- return lhs.key < rhs;
- }
-};
-
-} // namespace
-
-static auto& g_properties_lock = *new std::mutex;
-static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
-
-static bool property_set(const char* key, const char* value) {
- if (key == nullptr || *key == '\0') return false;
- if (value == nullptr) value = "";
- bool read_only = !strncmp(key, "ro.", 3);
- if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
-
- std::lock_guard lock(g_properties_lock);
- auto [it, success] = g_properties.emplace(key, value);
- if (read_only) return success;
- if (!success) {
- it->value = value;
- ++it->serial;
- }
- return true;
-}
-
-template <typename Func>
-static void property_get(const char* key, Func callback) {
- std::lock_guard lock(g_properties_lock);
- auto it = g_properties.find(key);
- if (it != g_properties.end()) {
- callback(*it);
- }
-}
-
-// Redefine the __system_property_XXX functions here so we can perform
-// logging and access checks for all sysprops in native code.
-
-static void check_system_property_access(const char* key, bool write);
-
-extern "C" {
-
-int __system_property_set(const char* key, const char* value) {
- check_system_property_access(key, true);
- return property_set(key, value) ? 0 : -1;
-}
-
-int __system_property_get(const char* key, char* value) {
- check_system_property_access(key, false);
- *value = '\0';
- property_get(key, [&](const prop_info& info) {
- snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
- });
- return strlen(value);
-}
-
-const prop_info* __system_property_find(const char* key) {
- check_system_property_access(key, false);
- const prop_info* pi = nullptr;
- property_get(key, [&](const prop_info& info) { pi = &info; });
- return pi;
-}
-
-void __system_property_read_callback(const prop_info* pi,
- void (*callback)(void*, const char*, const char*, uint32_t),
- void* cookie) {
- std::lock_guard lock(g_properties_lock);
- callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
-}
-
-} // extern "C"
-
-// ---- JNI ----
-
-static JavaVM* gVM = nullptr;
-static jclass gRunnerState = nullptr;
-static jmethodID gCheckSystemPropertyAccess;
-
-static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
- ScopedUtfChars path(env, javaPath);
- // Force reload ourselves as global
- dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
-}
-
-// Call back into Java code to check property access
-static void check_system_property_access(const char* key, bool write) {
- if (gVM != nullptr && gRunnerState != nullptr) {
- JNIEnv* env;
- if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
- ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
- env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
- env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
- return;
- }
- }
- // Not on JVM thread, abort
- LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
-}
-
-static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
- ScopedUtfChars key(env, javaKey);
- jstring value = nullptr;
- property_get(key.c_str(),
- [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
- return value;
-}
-
-static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
- ScopedUtfChars key(env, javaKey);
- ScopedUtfChars value(env, javaValue);
- return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
- std::lock_guard lock(g_properties_lock);
-
- if (javaKey == nullptr) {
- g_properties.clear();
- return JNI_TRUE;
- } else {
- ScopedUtfChars key(env, javaKey);
- auto it = g_properties.find(key);
- if (it != g_properties.end()) {
- g_properties.erase(it);
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
- }
-}
-
-static const JNINativeMethod sMethods[] = {
- {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
- {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
- {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
- {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
-};
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- ALOGI("%s: JNI_OnLoad", __FILE__);
-
- JNIEnv* env = GetJNIEnvOrDie(vm);
- gVM = vm;
-
- // Fetch several references for future use
- gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
- gCheckSystemPropertyAccess =
- GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
- "(Ljava/lang/String;Z)V");
-
- // Expose raw property methods as JNI methods
- jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
- if (res < 0) return -1;
-
- return JNI_VERSION_1_4;
-}
diff --git a/ravenwood/scripts/extract-last-soong-commands.py b/ravenwood/scripts/extract-last-soong-commands.py
new file mode 100755
index 000000000000..bdc1de0c44f4
--- /dev/null
+++ b/ravenwood/scripts/extract-last-soong-commands.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+# 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.
+
+
+# This script extracts all the commands executed in the last soong run,
+# and write them into a script file, and print the filename.
+#
+# All the commands are commented out. Uncomment what you want to execute as
+# needed before running it.
+
+import datetime
+import gzip
+import os
+import re
+import shlex
+import sys
+
+re_command = re.compile(r''' ^\[.*?\] \s* (.*) ''', re.X)
+
+HEADER = r'''#!/bin/bash
+
+set -e # Stop on a failed command
+
+cd "${ANDROID_BUILD_TOP:?}"
+
+'''
+
+OUT_SCRIPT_DIR = "/tmp/"
+OUT_SCRIPT_FORMAT = "soong-rerun-%Y-%m-%d_%H-%M-%S.sh"
+
+def main(args):
+ log = os.environ["ANDROID_BUILD_TOP"] + "/out/verbose.log.gz"
+ outdir = "/tmp/"
+ outfile = outdir + datetime.datetime.now().strftime(OUT_SCRIPT_FORMAT)
+
+ with open(outfile, "w") as out:
+ out.write(HEADER)
+
+ with gzip.open(log) as f:
+ for line in f:
+ s = line.decode("utf-8")
+
+ if s.startswith("verbose"):
+ continue
+ if re.match('^\[.*bootstrap blueprint', s):
+ continue
+
+ s = s.rstrip()
+
+ m = re_command.search(s)
+ if m:
+ command = m.groups()[0]
+
+ out.write('#========\n')
+
+ # Show the full command line before executing it.
+ out.write('#echo ' + shlex.quote(command) + '\n')
+ out.write('\n')
+
+ # Execute the command.
+ out.write('#' + command + '\n')
+
+ out.write('\n')
+
+ continue
+
+ if s.startswith("FAILED:"):
+ break
+
+ os.chmod(outfile, 0o755)
+ print(outfile)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index c29fb7f67e78..6d82a744bc4f 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -106,45 +106,6 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this
> **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward.
-## Strategies for feature flags
-
-Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team:
-
-```
-import android.platform.test.flag.junit.SetFlagsRule;
-
-@RunWith(AndroidJUnit4.class)
-public class MyCodeTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
-
- @Test
- public void testEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG);
- // verify test logic that depends on flag being enabled
- }
-```
-
-This naturally composes together well with any `RavenwoodRule` that your test may need.
-
-While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS). Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors:
-
-```
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-@RunWith(AndroidJUnit4.class)
-public class MyCodeTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
-```
-
-Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors. The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood.
-
## Strategies for migration/bivalent tests
Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment.
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
deleted file mode 100644
index 306c2b39c70d..000000000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
+++ /dev/null
@@ -1,42 +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.ravenwoodtest.bivalenttest;
-
-import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test to make sure the config field is used.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodConfigTest {
- private static final String PACKAGE_NAME = "com.android.ravenwoodtest.bivalenttest";
-
- @Test
- public void testConfig() {
- assumeTrue(isOnRavenwood());
- assertEquals(PACKAGE_NAME,
- InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
- }
-}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
new file mode 100644
index 000000000000..74c1f3f686aa
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.Test;
+
+public class RavenwoodLogLevelTest {
+ /**
+ * Assert that the `priority` is loggable, but one level below is not.
+ */
+ private void assertBarelyLoggable(String tag, int priority) {
+ assertEquals(true, Log.isLoggable(tag, priority));
+ assertEquals(false, Log.isLoggable(tag, priority - 1));
+ }
+
+ @Test
+ public void testDefaultLogTags() {
+ RavenwoodRule.setAndroidLogTags(null);
+
+ // Info should always be loggable.
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllVerbose() {
+ RavenwoodRule.setAndroidLogTags("*:V");
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllSilent() {
+ RavenwoodRule.setAndroidLogTags("*:S");
+
+ assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+ assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+ }
+
+ @Test
+ public void testComplex() {
+ RavenwoodRule.setAndroidLogTags("TAG1:W TAG2:D *:I");
+
+ assertBarelyLoggable("TAG1", Log.WARN);
+ assertBarelyLoggable("TAG2", Log.DEBUG);
+ assertBarelyLoggable("TAG3", Log.INFO);
+ }
+
+ @Test
+ public void testAllVerbose_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.VERBOSE);
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+ assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+ assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+ assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+ }
+
+ @Test
+ public void testAllSilent_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.ASSERT + 1);
+
+ assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+ assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+ }
+
+ @Test
+ public void testComplex_setLogLevel() {
+ RavenwoodRule.setAndroidLogTags(null);
+ RavenwoodRule.setLogLevel(null, Log.INFO);
+ RavenwoodRule.setLogLevel("TAG1", Log.WARN);
+ RavenwoodRule.setLogLevel("TAG2", Log.DEBUG);
+
+ assertBarelyLoggable("TAG1", Log.WARN);
+ assertBarelyLoggable("TAG2", Log.DEBUG);
+ assertBarelyLoggable("TAG3", Log.INFO);
+ }
+}
diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt
index 08f5397730da..83c31512eb70 100644
--- a/ravenwood/texts/ravenwood-common-policies.txt
+++ b/ravenwood/texts/ravenwood-common-policies.txt
@@ -14,7 +14,7 @@ class :r keepclass
# Support APIs not available in standard JRE
class java.io.FileDescriptor keep
- method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$
- method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$
+ method getInt$ @com.android.ravenwood.RavenwoodJdkPatch.getInt$
+ method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$
class java.util.LinkedHashMap keep
- method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest
+ method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 59fa464a7212..fc885d6f463b 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -66,6 +66,9 @@ class InMemoryOutputFilter(
methodName: String,
descriptor: String
) {
+ if (descriptor == "*") {
+ return
+ }
if (classes.findMethod(className, methodName, descriptor) == null) {
log.w("Unknown method $className.$methodName$descriptor")
}
@@ -92,7 +95,8 @@ class InMemoryOutputFilter(
descriptor: String,
): FilterPolicyWithReason {
return mPolicies[getMethodKey(className, methodName, descriptor)]
- ?: super.getPolicyForMethod(className, methodName, descriptor)
+ ?: mPolicies[getMethodKey(className, methodName, "*")]
+ ?: super.getPolicyForMethod(className, methodName, descriptor)
}
fun setPolicyForMethod(
@@ -107,7 +111,8 @@ class InMemoryOutputFilter(
override fun getRenameTo(className: String, methodName: String, descriptor: String): String? {
return mRenames[getMethodKey(className, methodName, descriptor)]
- ?: super.getRenameTo(className, methodName, descriptor)
+ ?: mRenames[getMethodKey(className, methodName, "*")]
+ ?: super.getRenameTo(className, methodName, descriptor)
}
fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index caf80ebec0c9..7462a8ce12c5 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -303,12 +303,21 @@ class TextFileFilterPolicyParser(
}
private fun parseMethod(fields: Array<String>) {
- if (fields.size < 4) {
- throw ParseException("Method ('m') expects 3 fields.")
+ if (fields.size < 3 || fields.size > 4) {
+ throw ParseException("Method ('m') expects 3 or 4 fields.")
}
val name = fields[1]
- val signature = fields[2]
- val policy = parsePolicy(fields[3])
+ val signature: String
+ val policyStr: String
+ if (fields.size <= 3) {
+ signature = "*"
+ policyStr = fields[2]
+ } else {
+ signature = fields[2]
+ policyStr = fields[3]
+ }
+
+ val policy = parsePolicy(policyStr)
if (!policy.isUsableWithMethods) {
throw ParseException("Method can't have policy '$policy'")
@@ -321,7 +330,7 @@ class TextFileFilterPolicyParser(
policy.withReason(FILTER_REASON)
)
if (policy == FilterPolicy.Substitute) {
- val fromName = fields[3].substring(1)
+ val fromName = policyStr.substring(1)
if (fromName == name) {
throw ParseException(
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
index d45f41407a52..a3f934cacc2c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
@@ -48,10 +48,11 @@ class TextFilePolicyMethodReplaceFilter(
// Maybe use 'Tri' if we end up having too many replacements.
spec.forEach {
if (className == it.fromClass &&
- methodName == it.fromMethod &&
- descriptor == it.fromDescriptor
+ methodName == it.fromMethod
) {
- return MethodReplaceTarget(it.toClass, it.toMethod)
+ if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
+ return MethodReplaceTarget(it.toClass, it.toMethod)
+ }
}
}
return null
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 3c138d21b75d..2f35d35d608d 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
@@ -3,11 +3,11 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy keep
# field remove remove # Implicitly remove
method <init> ()V keep
method addOne (I)I keep
- method addOneInner (I)I keep
+ method addOneInner keep
method toBeRemoved (Ljava/lang/String;)V remove
method addTwo (I)I @addTwo_host
# method addTwo_host (I)I # used as a substitute
- method nativeAddThree (I)I @addThree_host
+ method nativeAddThree @addThree_host
# method addThree_host (I)I # used as a substitute
method unsupportedMethod ()Ljava/lang/String; throw
method visibleButUsesUnsupportedMethod ()Ljava/lang/String; keep
diff --git a/services/Android.bp b/services/Android.bp
index fc0bb33e6e4e..a7cb9bb9af24 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -210,6 +210,35 @@ crashrecovery_java_defaults {
},
}
+soong_config_module_type {
+ name: "ondeviceintelligence_module_java_defaults",
+ module_type: "java_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_ondevice_intelligence_module",
+ "release_ondevice_intelligence_platform",
+ ],
+ properties: [
+ "libs",
+ "srcs",
+ "static_libs",
+ ],
+}
+
+// Conditionally add ondeviceintelligence stubs library
+ondeviceintelligence_module_java_defaults {
+ name: "ondeviceintelligence_conditionally",
+ soong_config_variables: {
+ release_ondevice_intelligence_module: {
+ libs: ["service-ondeviceintelligence.stubs.system_server"],
+ },
+ release_ondevice_intelligence_platform: {
+ srcs: [":service-ondeviceintelligence-sources"],
+ static_libs: ["modules-utils-backgroundthread"],
+ },
+ },
+}
+
// merge all required services into one jar
// ============================================================
soong_config_module_type {
@@ -236,6 +265,7 @@ system_java_library {
"services_java_defaults",
"art_profile_java_defaults",
"services_crashrecovery_stubs_conditionally",
+ "ondeviceintelligence_conditionally",
],
installable: true,
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a0b989b44f4f..ad87ceaf6f38 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -86,10 +86,17 @@ flag {
}
flag {
- name: "enable_hardware_shortcut_disables_warning"
- namespace: "accessibility"
- description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
- bug: "287065325"
+ name: "enable_hardware_shortcut_disables_warning"
+ namespace: "accessibility"
+ description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+ bug: "287065325"
+}
+
+flag {
+ name: "enable_low_vision_hats"
+ namespace: "accessibility"
+ description: "Use HaTS for low vision feedback."
+ bug: "380346799"
}
flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 7ef6aace1538..e1b6c9c5aa42 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -409,10 +409,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
final int eventSource = event.getSource();
final int displayId = event.getDisplayId();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
- if (!Flags.doNotResetKeyEventState()) {
- state.reset();
- clearEventStreamHandler(displayId, eventSource);
- }
if (DEBUG) {
Slog.d(TAG, "Not processing event " + event);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 71a0fc453a95..5c1ad74fac93 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1028,8 +1028,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
- Handler receiverHandler =
- Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null;
+ Handler receiverHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1071,8 +1070,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
newValue, restoredFromSdk);
}
}
+ // Currently in SUW, the user can't see gesture shortcut option as the
+ // navigation system is set to button navigation. We'll rely on the
+ // SettingsBackupAgent to restore the settings since we don't
+ // need to merge an empty gesture target.
case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
Settings.Secure.ACCESSIBILITY_QS_TARGETS,
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE ->
restoreShortcutTargets(newValue,
@@ -2257,10 +2259,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
- if (shortcutType == HARDWARE
- && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
- return;
- }
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
@@ -2269,8 +2267,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext, shortcutType, userState.mUserId))
: userState.getShortcutTargetsLocked(shortcutType);
- if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()
- && shortcutType == HARDWARE) {
+ if (shortcutType == HARDWARE) {
final String defaultService =
mContext.getString(R.string.config_defaultAccessibilityService);
final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
@@ -2930,27 +2927,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final String builderValue = builder.toString();
final String settingValue = TextUtils.isEmpty(builderValue)
? defaultEmptyString : builderValue;
- if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
- final String currentValue = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), settingName, userId);
- if (Objects.equals(settingValue, currentValue)) {
- // This logic exists to fix a bug where AccessibilityManagerService was writing
- // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
- // during setup, due to a race condition in package scanning making A11yMS think
- // that the default service was not installed.
- //
- // Writing `null` was implicitly causing that Setting to have the default
- // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
- // Setting altogether.
- //
- // The "quick fix" here is to not write `null` if the existing value is already
- // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
- // that allows override-by-restore, but the full repercussions of using that here
- // have not yet been evaluated.
- // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
- // "overridable by restore" when writing secure settings.
- return;
- }
+ final String currentValue = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), settingName, userId);
+ if (Objects.equals(settingValue, currentValue)) {
+ // This logic exists to fix a bug where AccessibilityManagerService was writing
+ // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
+ // during setup, due to a race condition in package scanning making A11yMS think
+ // that the default service was not installed.
+ //
+ // Writing `null` was implicitly causing that Setting to have the default
+ // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
+ // Setting altogether.
+ //
+ // The "quick fix" here is to not write `null` if the existing value is already
+ // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
+ // that allows override-by-restore, but the full repercussions of using that here
+ // have not yet been evaluated.
+ // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
+ // "overridable by restore" when writing secure settings.
+ return;
}
final long identity = Binder.clearCallingIdentity();
try {
@@ -6649,28 +6644,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
- if (Flags.managerPackageMonitorLogicFix()) {
- if (!doit) {
- // if we're not handling the stop here, then we only need to know
- // if any of the force-stopped packages are currently enabled.
- return userState.mEnabledServices.stream().anyMatch(
- (comp) -> Arrays.stream(packages).anyMatch(
- (pkg) -> pkg.equals(comp.getPackageName()))
- );
- } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
- mManagerService.onUserStateChangedLocked(userState);
- }
- return false;
- } else {
- // this old logic did not properly indicate when base packageMonitor's routine
- // should handle stopping the package.
- if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
- mManagerService.onUserStateChangedLocked(userState);
- return false;
- } else {
- return true;
- }
+ if (!doit) {
+ // if we're not handling the stop here, then we only need to know
+ // if any of the force-stopped packages are currently enabled.
+ return userState.mEnabledServices.stream().anyMatch(
+ (comp) -> Arrays.stream(packages).anyMatch(
+ (pkg) -> pkg.equals(comp.getPackageName()))
+ );
+ } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+ mManagerService.onUserStateChangedLocked(userState);
}
+ return false;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index f45fa921c4a2..5ae077363c88 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -405,10 +405,9 @@ public class AccessibilitySecurityPolicy {
* @throws SecurityException if the input method is not in the same package as the service.
*/
@AccessibilityService.SoftKeyboardController.EnableImeResult
- int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service)
- throws SecurityException {
+ int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service,
+ int callingUserId) throws SecurityException {
final String servicePackageName = service.getComponentName().getPackageName();
- final int callingUserId = UserHandle.getCallingUserId();
InputMethodInfo inputMethodInfo = null;
List<InputMethodInfo> inputMethodInfoList =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 15999d19ebc0..a3fe9ec5ea22 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -410,9 +410,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
final @AccessibilityService.SoftKeyboardController.EnableImeResult int checkResult;
final long identity = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this);
- }
+ checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this, callingUserId);
if (checkResult != ENABLE_IME_SUCCESS) {
return checkResult;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 8b870dbaa100..b7fd09f7b594 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -832,20 +832,12 @@ public class AccessibilityWindowManager {
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
- boolean hasWindowIgnore = false;
if (windowCount > 0) {
- for (int i = 0; i < windowCount; i++) {
- final WindowInfo windowInfo = windows.get(i);
- final AccessibilityWindowInfo window;
- if (mTrackingWindows) {
- window = populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
- if (window == null) {
- hasWindowIgnore = true;
- }
- } else {
- window = null;
- }
- if (window != null) {
+ if (mTrackingWindows) {
+ for (int i = 0; i < windowCount; i++) {
+ final WindowInfo windowInfo = windows.get(i);
+ final AccessibilityWindowInfo window =
+ populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
// Flip layers in list to be consistent with AccessibilityService#getWindows
window.setLayer(windowCount - 1 - window.getLayer());
@@ -870,13 +862,6 @@ public class AccessibilityWindowManager {
}
}
final int accessibilityWindowCount = mWindows.size();
- // Re-order the window layer of all windows in the windows list because there's
- // window not been added into the windows list.
- if (hasWindowIgnore) {
- for (int i = 0; i < accessibilityWindowCount; i++) {
- mWindows.get(i).setLayer(accessibilityWindowCount - 1 - i);
- }
- }
if (isTopFocusedDisplay) {
if (mTouchInteractionInProgress && activeWindowGone) {
mActiveWindowId = mTopFocusedWindowId;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index da11a76d5282..f8551457d04d 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -183,14 +183,12 @@ public class ProxyManager {
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (mAppsOnVirtualDeviceListener == null) {
- mAppsOnVirtualDeviceListener = allRunningUids ->
- notifyProxyOfRunningAppsChange(allRunningUids);
- final VirtualDeviceManagerInternal localVdm = getLocalVdm();
- if (localVdm != null) {
- localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
- }
+ if (mAppsOnVirtualDeviceListener == null) {
+ mAppsOnVirtualDeviceListener = allRunningUids ->
+ notifyProxyOfRunningAppsChange(allRunningUids);
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null) {
+ localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
}
}
if (mProxyA11yServiceConnections.size() == 1) {
@@ -331,14 +329,12 @@ public class ProxyManager {
// device.
if (!isProxyedDeviceId(deviceId)) {
synchronized (mLock) {
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (mProxyA11yServiceConnections.size() == 0) {
- final VirtualDeviceManagerInternal localVdm = getLocalVdm();
- if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
- localVdm.unregisterAppsOnVirtualDeviceListener(
- mAppsOnVirtualDeviceListener);
- mAppsOnVirtualDeviceListener = null;
- }
+ if (mProxyA11yServiceConnections.size() == 0) {
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
+ localVdm.unregisterAppsOnVirtualDeviceListener(
+ mAppsOnVirtualDeviceListener);
+ mAppsOnVirtualDeviceListener = null;
}
}
mSystemSupport.removeDeviceIdLocked(deviceId);
@@ -671,8 +667,7 @@ public class ProxyManager {
+ getLastSentStateLocked(deviceId));
Slog.v(LOG_TAG, "force update: " + forceUpdate);
}
- if ((getLastSentStateLocked(deviceId)) != proxyState
- || (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) {
+ if ((getLastSentStateLocked(deviceId)) != proxyState || forceUpdate) {
setLastStateLocked(deviceId, proxyState);
mMainHandler.post(() -> {
synchronized (mLock) {
@@ -873,33 +868,22 @@ public class ProxyManager {
for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
final AccessibilityManagerService.Client client =
((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
- if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
- if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
- continue;
- }
- boolean uidBelongsToDevice =
- localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
- if (client.mDeviceId != deviceId && uidBelongsToDevice) {
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
- + Arrays.toString(client.mPackageNames));
- }
- client.mDeviceId = deviceId;
- } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
- client.mDeviceId = DEVICE_ID_DEFAULT;
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to the default device from device id "
- + deviceId + " are " + Arrays.toString(client.mPackageNames));
- }
+ if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
+ continue;
+ }
+ boolean uidBelongsToDevice =
+ localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
+ if (client.mDeviceId != deviceId && uidBelongsToDevice) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ + Arrays.toString(client.mPackageNames));
}
- } else {
- if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
- && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
- if (DEBUG) {
- Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
- + Arrays.toString(client.mPackageNames));
- }
- client.mDeviceId = deviceId;
+ client.mDeviceId = deviceId;
+ } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
+ client.mDeviceId = DEVICE_ID_DEFAULT;
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to the default device from device id "
+ + deviceId + " are " + Arrays.toString(client.mPackageNames));
}
}
}
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 0ed239e442e7..0cbbf6da022b 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -240,10 +240,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
private void clear(MotionEvent event, int policyFlags) {
- if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
- // If a touch exploration gesture is in progress send events for its end.
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
mDraggingPointerId = INVALID_POINTER_ID;
// Send exit to any pointers that we have delivered as part of delegating or dragging.
mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
@@ -562,10 +559,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// clear any hover events that might have been queued and never sent.
mSendHoverEnterAndMoveDelayed.clear();
mSendHoverExitDelayed.cancel();
- // If a touch exploration gesture is in progress send events for its end.
- if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
if (mState.isClear()) {
if (!mSendHoverEnterAndMoveDelayed.isPending()) {
// Queue a delayed transition to STATE_TOUCH_EXPLORING.
@@ -1599,9 +1593,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (mEvents.size() == 0) {
return;
}
- if (Flags.sendHoverEventsBasedOnEventStream()) {
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
- }
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 6b48d2bacf9d..a4568aaa7a0d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -31,7 +31,6 @@ import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
import com.android.internal.R;
-import com.android.server.accessibility.Flags;
/**
* Handles the behavior while receiving scaling and panning gestures if it's enabled.
@@ -73,13 +72,9 @@ class PanningScalingHandler extends
mMaxScale = maxScale;
mMinScale = minScale;
mBlockScroll = blockScroll;
- if (Flags.pinchZoomZeroMinSpan()) {
- mScaleGestureDetector = new ScaleGestureDetector(context,
- ViewConfiguration.get(context).getScaledTouchSlop() * 2,
- /* minSpan= */ 0, Handler.getMain(), this);
- } else {
- mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
- }
+ mScaleGestureDetector = new ScaleGestureDetector(context,
+ ViewConfiguration.get(context).getScaledTouchSlop() * 2,
+ /* minSpan= */ 0, Handler.getMain(), this);
mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
mMagnificationDelegate = magnificationDelegate;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index f13e22950e2d..c17c34061d1b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -24,12 +24,12 @@ import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_E
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.AppFunctionManager;
import android.app.appfunctions.AppFunctionManagerHelper;
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
-import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
@@ -158,8 +158,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
} catch (SecurityException exception) {
safeExecuteAppFunctionCallback.onError(
new AppFunctionException(
- AppFunctionException.ERROR_DENIED,
- exception.getMessage()));
+ AppFunctionException.ERROR_DENIED, exception.getMessage()));
return null;
}
@@ -195,12 +194,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
@NonNull IBinder callerBinder) {
UserHandle targetUser = requestInternal.getUserHandle();
- // TODO(b/354956319): Add and honor the new enterprise policies.
- if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) {
safeExecuteAppFunctionCallback.onError(
- new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
- "Cannot run on a device with a device owner or from the managed"
- + " profile."));
+ new AppFunctionException(
+ AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED,
+ "Cannot run on a user with a restricted enterprise policy"));
return;
}
@@ -442,7 +441,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
safeExecuteAppFunctionCallback.onError(
- new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
+ new AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
"Failed to bind the AppFunctionService."));
}
}
@@ -495,8 +495,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
return;
}
FutureGlobalSearchSession futureGlobalSearchSession =
- new FutureGlobalSearchSession(
- perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR);
+ new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR);
AppFunctionMetadataObserver appFunctionMetadataObserver =
new AppFunctionMetadataObserver(
user.getUserHandle(),
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 5393b939b5ed..61917676e88d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -81,10 +81,12 @@ public interface CallerValidator {
@NonNull String functionId);
/**
- * Checks if the user is organization managed.
+ * Checks if the app function policy is allowed.
*
+ * @param callingUser The current calling user.
* @param targetUser The user which the caller is requesting to execute as.
- * @return Whether the user is organization managed.
+ * @return Whether the app function policy is allowed.
*/
- boolean isUserOrganizationManaged(@NonNull UserHandle targetUser);
+ boolean verifyEnterprisePolicyIsAllowed(
+ @NonNull UserHandle callingUser, @NonNull UserHandle targetUser);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index e85a70d5845a..69481c32baf0 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -28,6 +28,7 @@ import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchManager.SearchContext;
@@ -39,7 +40,6 @@ import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
import com.android.internal.infra.AndroidFuture;
@@ -124,8 +124,7 @@ class CallerValidatorImpl implements CallerValidator {
FutureAppSearchSession futureAppSearchSession =
new FutureAppSearchSessionImpl(
Objects.requireNonNull(
- mContext
- .createContextAsUser(targetUser, 0)
+ mContext.createContextAsUser(targetUser, 0)
.getSystemService(AppSearchManager.class)),
THREAD_POOL_EXECUTOR,
new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build());
@@ -168,13 +167,16 @@ class CallerValidatorImpl implements CallerValidator {
}
@Override
- public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) {
- if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class))
- .isDeviceManaged()) {
- return true;
- }
- return Objects.requireNonNull(mContext.getSystemService(UserManager.class))
- .isManagedProfile(targetUser.getIdentifier());
+ public boolean verifyEnterprisePolicyIsAllowed(
+ @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
+ @AppFunctionsPolicy
+ int callingUserPolicy = getDevicePolicyManagerAsUser(callingUser).getAppFunctionsPolicy();
+ @AppFunctionsPolicy
+ int targetUserPolicy = getDevicePolicyManagerAsUser(targetUser).getAppFunctionsPolicy();
+ boolean isSameUser = callingUser.equals(targetUser);
+
+ return isAppFunctionPolicyAllowed(targetUserPolicy, isSameUser)
+ && isAppFunctionPolicyAllowed(callingUserPolicy, isSameUser);
}
/**
@@ -264,4 +266,18 @@ class CallerValidatorImpl implements CallerValidator {
return Process.INVALID_UID;
}
}
+
+ private boolean isAppFunctionPolicyAllowed(
+ @AppFunctionsPolicy int userPolicy, boolean isSameUser) {
+ return switch (userPolicy) {
+ case DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY -> true;
+ case DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE -> isSameUser;
+ default -> false;
+ };
+ }
+
+ private DevicePolicyManager getDevicePolicyManagerAsUser(@NonNull UserHandle targetUser) {
+ return mContext.createContextAsUser(targetUser, /* flags= */ 0)
+ .getSystemService(DevicePolicyManager.class);
+ }
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 762665c00e05..cffdfbd36532 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -330,6 +330,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Handler to the background thread that saves states to disk.
private Handler mSaveStateHandler;
+
+ private Handler mAlarmHandler;
// Handler to the background thread that saves generated previews to disk. All operations that
// modify saved previews must be run on this Handler.
private Handler mSavePreviewsHandler;
@@ -373,6 +375,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (removeAppWidgetServiceIoFromCriticalPath()) {
mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
this::handleSaveMessage);
+ mAlarmHandler = new Handler(BackgroundThread.get().getLooper());
} else {
mSaveStateHandler = BackgroundThread.getHandler();
}
@@ -2739,10 +2742,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
if (provider.broadcast != null) {
final PendingIntent broadcast = provider.broadcast;
- mSaveStateHandler.post(() -> {
- mAlarmManager.cancel(broadcast);
- broadcast.cancel();
- });
+ Runnable cancelRunnable = () -> {
+ mAlarmManager.cancel(broadcast);
+ broadcast.cancel();
+ };
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mAlarmHandler.post(cancelRunnable);
+ } else {
+ mSaveStateHandler.post(cancelRunnable);
+ }
provider.broadcast = null;
}
}
@@ -3422,10 +3430,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// invariant and established the PendingIntent safely.
final long period = Math.max(info.updatePeriodMillis, MIN_UPDATE_PERIOD);
final PendingIntent broadcast = provider.broadcast;
- mSaveStateHandler.post(() ->
+
+ Runnable repeatRunnable = () -> {
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + period, period, broadcast)
- );
+ SystemClock.elapsedRealtime() + period, period, broadcast);
+ };
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mAlarmHandler.post(repeatRunnable);
+ } else {
+ mSaveStateHandler.post(repeatRunnable);
+ }
}
}
}
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 5d2ef770b96b..5e1b1473d233 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -23,6 +23,16 @@ flag {
}
flag {
+ name: "relayout_fix"
+ namespace: "autofill"
+ description: "Fixing relayout issue. Guarding enabling device config flags"
+ bug: "381226145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_invisible_view_group_in_assist_structure"
namespace: "autofill"
description: "Mitigation for autofill providers miscalculating view visibility"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index b3fe5f234bc2..b78d103f287c 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,10 +2,18 @@ package: "android.service.autofill"
container: "system"
flag {
+ name: "autofill_session_destroyed"
+ namespace: "autofill"
+ description: "Guards against new metrics definitions introduced in W"
+ bug: "342676602"
+}
+
+flag {
name: "autofill_w_metrics"
namespace: "autofill"
description: "Guards against new metrics definitions introduced in W"
bug: "342676602"
+ is_exported: true
}
flag {
@@ -31,9 +39,24 @@ flag {
}
flag {
+ name: "fill_dialog_improvements_impl"
+ is_exported: true
+ namespace: "autofill"
+ description: "Improvements for Fill Dialog for non-api changes"
+ bug: "336223371"
+}
+
+flag {
name: "fill_dialog_improvements"
is_exported: true
namespace: "autofill"
description: "Improvements for Fill Dialog, including deprecation of pre-trigger API's"
bug: "336223371"
}
+
+flag {
+ name: "add_last_focused_id_to_fill_event_history"
+ namespace: "autofill"
+ description: "Adds focused id to each event that's part of the fill event history"
+ bug: "334141398"
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index b52c65054e51..5cf96bfb2b8b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -61,6 +61,7 @@ import android.service.autofill.FillEventHistory;
import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.FillEventHistory.Event.NoSaveReason;
import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
import android.service.autofill.IAutoFillService;
import android.service.autofill.InlineSuggestionRenderService;
import android.service.autofill.SaveInfo;
@@ -100,6 +101,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
+
/**
* Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
* app's {@link IAutoFillService} implementation.
@@ -748,6 +750,22 @@ final class AutofillManagerServiceImpl
@GuardedBy("mLock")
void removeSessionLocked(int sessionId) {
mSessions.remove(sessionId);
+ if (Flags.autofillSessionDestroyed()) {
+ if (sVerbose) {
+ Slog.v(
+ TAG,
+ "removeSessionLocked(): removed " + sessionId);
+ }
+ RemoteFillService remoteService =
+ new RemoteFillService(
+ getContext(),
+ mInfo.getServiceInfo().getComponentName(),
+ mUserId,
+ /* callbacks= */ null,
+ mMaster.isInstantServiceAllowed(),
+ /* credentialAutofillService= */ null);
+ remoteService.onSessionDestroyed(null);
+ }
}
/**
@@ -900,13 +918,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill selection when an authentication was selected.
*/
void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
- int uiType) {
+ int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
null, null, null, null, null, null,
- NO_SAVE_UI_REASON_NONE, uiType));
+ NO_SAVE_UI_REASON_NONE, uiType, focusedId));
}
}
}
@@ -915,13 +933,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill selection when an dataset authentication was selected.
*/
void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
- @Nullable Bundle clientState, int uiType) {
+ @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
clientState, null, null, null, null, null, null, null, null,
- NO_SAVE_UI_REASON_NONE, uiType));
+ NO_SAVE_UI_REASON_NONE, uiType, focusedId));
}
}
}
@@ -933,7 +951,7 @@ final class AutofillManagerServiceImpl
synchronized (mLock) {
if (isValidEventLocked("logSaveShown()", sessionId)) {
mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
- null, null, null, null, null, null, null));
+ null, null, null, null, null, null, null, /* focusedId= */ null));
}
}
}
@@ -942,13 +960,13 @@ final class AutofillManagerServiceImpl
* Updates the last fill response when a dataset was selected.
*/
void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
- @Nullable Bundle clientState, int uiType) {
+ @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetSelected()", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- uiType));
+ uiType, focusedId));
}
}
}
@@ -956,13 +974,14 @@ final class AutofillManagerServiceImpl
/**
* Updates the last fill response when a dataset is shown.
*/
- void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType) {
+ void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType,
+ @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetShown", sessionId)) {
mEventHistory.addEvent(
new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- uiType));
+ uiType, focusedId));
}
}
}
@@ -970,7 +989,8 @@ final class AutofillManagerServiceImpl
/**
* Updates the last fill response when a view was entered.
*/
- void logViewEntered(int sessionId, @Nullable Bundle clientState) {
+ void logViewEntered(int sessionId, @Nullable Bundle clientState,
+ @Nullable AutofillId focusedId) {
synchronized (mLock) {
if (!isValidEventLocked("logViewEntered", sessionId)) {
return;
@@ -988,7 +1008,7 @@ final class AutofillManagerServiceImpl
mEventHistory.addEvent(
new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
- null, null, null, null, null, null, null));
+ null, null, null, null, null, null, null, focusedId));
}
}
@@ -1001,7 +1021,8 @@ final class AutofillManagerServiceImpl
}
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
- clientState, null, null, null, null, null, null, null, null));
+ clientState, null, null, null, null, null, null, null, null,
+ /* focusedId= */ null));
}
}
@@ -1014,7 +1035,7 @@ final class AutofillManagerServiceImpl
}
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
- null, null, null, null, null, null));
+ null, null, null, null, null, null, /* focusedId= */ null));
}
}
@@ -1029,7 +1050,7 @@ final class AutofillManagerServiceImpl
mAugmentedAutofillEventHistory.addEvent(
new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
- UI_TYPE_INLINE));
+ UI_TYPE_INLINE, /* focusedId= */ null));
}
}
@@ -1113,7 +1134,8 @@ final class AutofillManagerServiceImpl
clientState, selectedDatasets, ignoredDatasets,
changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
- detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason));
+ detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason,
+ /* focusedId= */ null));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 07f5dcc3cb0a..f1e888400d32 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -34,6 +34,7 @@ import android.os.RemoteException;
import android.service.autofill.AutofillService;
import android.service.autofill.ConvertCredentialRequest;
import android.service.autofill.ConvertCredentialResponse;
+import android.service.autofill.FillEventHistory;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
@@ -497,6 +498,14 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}));
}
+ public void onSessionDestroyed(@Nullable FillEventHistory history) {
+ boolean success = run(service -> {
+ service.onSessionDestroyed(history);
+ });
+
+ if (sVerbose) Slog.v(TAG, "called onSessionDestroyed(): " + success);
+ }
+
void onSavedPasswordCountRequest(IResultReceiver receiver) {
run(service -> service.onSavedPasswordCountRequest(receiver));
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8f12b1db8f29..6b227d7a876e 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1871,7 +1871,7 @@ final class Session
if (mLogViewEntered) {
mLogViewEntered = false;
- mService.logViewEntered(id, null);
+ mService.logViewEntered(id, null, mCurrentViewId);
}
}
@@ -2775,9 +2775,9 @@ final class Session
forceRemoveFromServiceLocked();
return;
}
+ mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId);
}
- mService.setAuthenticationSelected(id, mClientState, uiType);
final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
mHandler.sendMessage(
@@ -2850,7 +2850,7 @@ final class Session
if (!mLoggedInlineDatasetShown) {
// Chip inflation already logged, do not log again.
// This is needed because every chip inflation will call this.
- mService.logDatasetShown(this.id, mClientState, uiType);
+ mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
}
mLoggedInlineDatasetShown = true;
@@ -2858,7 +2858,7 @@ final class Session
mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
// Explicitly sets maybeSetSuggestionPresentedTimestampMs
mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
- mService.logDatasetShown(this.id, mClientState, uiType);
+ mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
}
}
@@ -5139,7 +5139,7 @@ final class Session
// so this calling logViewEntered will be a nop.
// Calling logViewEntered() twice will only log it once
// TODO(271181979): this is broken for multiple partitions
- mService.logViewEntered(this.id, null);
+ mService.logViewEntered(this.id, null, mCurrentViewId);
}
// If this is the first time view is entered for inline, the last
@@ -6657,7 +6657,8 @@ final class Session
// Autofill it directly...
if (dataset.getAuthentication() == null) {
if (generateEvent) {
- mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType);
+ mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType,
+ mCurrentViewId);
}
if (mCurrentViewId != null) {
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -6667,7 +6668,8 @@ final class Session
}
// ...or handle authentication.
- mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType);
+ mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType,
+ mCurrentViewId);
mPresentationStatsEventLogger.maybeSetAuthenticationType(
AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
// does not matter the value of isPrimary because null response won't be overridden.
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index fcb7934f7ca0..b4adad2c8bef 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -68,4 +68,20 @@ flag {
"B&R operations in certain cases."
bug: "376661510"
is_fixed_read_only: true
+ is_exported: true
+}
+
+flag {
+ name: "enable_read_all_external_storage_files"
+ is_exported: true
+ namespace: "onboarding"
+ description: "Enables read all external storage files"
+ bug: "376598575"
+}
+flag {
+ name: "enable_metrics_settings_backup_agents"
+ namespace: "onboarding"
+ description: "Enable SettingsBackupAgent to collect B&R agent metrics."
+ bug: "379861078"
+ is_fixed_read_only: true
}
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
index 02f186557ca4..6ced096e8778 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -101,11 +101,16 @@ public class BackupAgentConnectionManager {
private static final class BackupAgentConnection {
public final ApplicationInfo appInfo;
+ public final int backupMode;
+ public final boolean inRestrictedMode;
public IBackupAgent backupAgent;
public boolean connecting = true; // Assume we are trying to connect on creation.
- private BackupAgentConnection(ApplicationInfo appInfo) {
+ private BackupAgentConnection(ApplicationInfo appInfo, int backupMode,
+ boolean inRestrictedMode) {
this.appInfo = appInfo;
+ this.backupMode = backupMode;
+ this.inRestrictedMode = inRestrictedMode;
}
}
@@ -113,26 +118,31 @@ public class BackupAgentConnectionManager {
* Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
* {@link #agentConnected(String, IBinder)}) or until this operation times out.
*
- * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
+ * @param backupMode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
*/
@Nullable
- public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
+ public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int backupMode,
@BackupAnnotations.BackupDestination int backupDestination) {
+ if (app == null) {
+ Slog.w(TAG, mUserIdMsg + "bindToAgentSynchronous for null app");
+ return null;
+ }
+
synchronized (mAgentConnectLock) {
- boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+ boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(backupMode,
app.packageName);
if (mCurrentConnection != null) {
Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
+ mCurrentConnection.appInfo.packageName);
}
- mCurrentConnection = new BackupAgentConnection(app);
+ mCurrentConnection = new BackupAgentConnection(app, backupMode, useRestrictedMode);
// bindBackupAgent() is an async API. It will kick off the app's process and call
// agentConnected() when it receives the agent from the app.
boolean startedBindSuccessfully = false;
try {
- startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName, mode,
- mUserId, backupDestination, useRestrictedMode);
+ startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName,
+ backupMode, mUserId, backupDestination, useRestrictedMode);
} catch (RemoteException e) {
// can't happen - ActivityManager is local
}
@@ -173,28 +183,66 @@ public class BackupAgentConnectionManager {
/**
* Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
* It will tell the app to destroy the agent.
+ *
+ * <p>If {@code allowKill} is set, this will kill the app's process if the app is in restricted
+ * mode or if it was started for restore and specified {@code android:killAfterRestore} in its
+ * manifest.
+ *
+ * @see #shouldUseRestrictedBackupModeForPackage(int, String)
*/
- public void unbindAgent(ApplicationInfo app) {
- synchronized (mAgentConnectLock) {
- if (mCurrentConnection == null) {
- Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
- } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
- Slog.w(TAG, mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
- + " expected: " + mCurrentConnection.appInfo.packageName);
- } else {
- mCurrentConnection = null;
- }
+ public void unbindAgent(ApplicationInfo app, boolean allowKill) {
+ if (app == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent for null app");
+ return;
+ }
+ synchronized (mAgentConnectLock) {
// Even if we weren't expecting to be bound to this agent, we should still call
// ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
try {
mActivityManager.unbindBackupAgent(app);
+
+ // Evaluate this before potentially setting mCurrentConnection = null.
+ boolean willKill = allowKill && shouldKillAppOnUnbind(app);
+
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
+ Slog.w(TAG,
+ mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
+ + " expected: " + mCurrentConnection.appInfo.packageName);
+ } else {
+ mCurrentConnection = null;
+ }
+
+ if (willKill) {
+ Slog.i(TAG, mUserIdMsg + "Killing agent host process");
+ mActivityManager.killApplicationProcess(app.processName, app.uid);
+ }
} catch (RemoteException e) {
// Can't happen - activity manager is local
}
}
}
+ @GuardedBy("mAgentConnectLock")
+ private boolean shouldKillAppOnUnbind(ApplicationInfo app) {
+ // We don't ask system UID processes to be killed.
+ if (UserHandle.isCore(app.uid)) {
+ return false;
+ }
+
+ // If the app is in restricted mode or if we're not sure if it is because our internal
+ // state is messed up, we need to avoid it being stuck in it.
+ if (mCurrentConnection == null || mCurrentConnection.inRestrictedMode) {
+ return true;
+ }
+
+ // App was doing restore and asked to be killed afterwards.
+ return isBackupModeRestore(mCurrentConnection.backupMode)
+ && (app.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0;
+ }
+
/**
* Callback: a requested backup agent has been instantiated. This should only be called from
* the {@link ActivityManager} when it's telling us that an agent is ready after a call to
@@ -307,16 +355,16 @@ public class BackupAgentConnectionManager {
*/
private boolean shouldUseRestrictedBackupModeForPackage(
@BackupAnnotations.OperationType int mode, String packageName) {
- if (!Flags.enableRestrictedModeChanges()) {
- return true;
- }
-
// Key/Value apps are never put in restricted mode.
if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
|| mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
return false;
}
+ if (!Flags.enableRestrictedModeChanges()) {
+ return true;
+ }
+
try {
PackageManager.Property property = mPackageManager.getPropertyAsUser(
PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
@@ -352,6 +400,11 @@ public class BackupAgentConnectionManager {
return true;
}
+ private static boolean isBackupModeRestore(int backupMode) {
+ return backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL;
+ }
+
@VisibleForTesting
Thread getThreadForCancellation(Runnable operation) {
return new Thread(operation, /* operationName */ "agent-disconnected");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 136bacdd6399..b343ec8e709b 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -300,7 +300,8 @@ public class KeyValueAdbBackupEngine {
}
private void cleanup() {
- mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo, /* allowKill= */ true);
mBlankStateName.delete();
mNewStateName.delete();
mBackupDataName.delete();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index e085f6e63067..3025e2eaede0 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -314,8 +314,6 @@ public class UserBackupManagerService {
private static final String SERIAL_ID_FILE = "serial_id";
- private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
-
private final @UserIdInt int mUserId;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final TransportManager mTransportManager;
@@ -1998,39 +1996,6 @@ public class UserBackupManagerService {
return mOperationStorage.isBackupOperationInProgress();
}
- /** Unbind the backup agent and kill the app if it's a non-system app. */
- public void tearDownAgentAndKill(ApplicationInfo app) {
- if (app == null) {
- // Null means the system package, so just quietly move on. :)
- return;
- }
-
- try {
- // unbind and tidy up even on timeout or failure, just in case
- mActivityManager.unbindBackupAgent(app);
-
- // The agent was running with a stub Application object, so shut it down.
- // !!! We hardcode the confirmation UI's package name here rather than use a
- // manifest flag! TODO something less direct.
- if (!UserHandle.isCore(app.uid)
- && !app.packageName.equals("com.android.backupconfirm")) {
- if (MORE_DEBUG) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "Killing agent host process"));
- }
- mActivityManager.killApplicationProcess(app.processName, app.uid);
- } else {
- if (MORE_DEBUG) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId, "Not killing after operation: " + app.processName));
- }
- }
- } catch (RemoteException e) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "Lost app trying to shut down"));
- }
- }
-
// ----- Full-data backup scheduling -----
/**
@@ -3536,40 +3501,6 @@ public class UserBackupManagerService {
}
}
- /**
- * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_packages' is
- * set to true in secure settings. See b/153940088 for details.
- *
- * TODO(b/154822946): Remove this logic in the next release.
- */
- public List<PackageInfo> filterUserFacingPackages(List<PackageInfo> packages) {
- if (!shouldSkipUserFacingData()) {
- return packages;
- }
-
- List<PackageInfo> filteredPackages = new ArrayList<>(packages.size());
- for (PackageInfo packageInfo : packages) {
- if (!shouldSkipPackage(packageInfo.packageName)) {
- filteredPackages.add(packageInfo);
- } else {
- Slog.i(TAG, "Will skip backup/restore for " + packageInfo.packageName);
- }
- }
-
- return filteredPackages;
- }
-
- @VisibleForTesting
- public boolean shouldSkipUserFacingData() {
- return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_PACKAGES,
- /* def */ 0) != 0;
- }
-
- @VisibleForTesting
- public boolean shouldSkipPackage(String packageName) {
- return WALLPAPER_PACKAGE.equals(packageName);
- }
-
private void updateStateForTransport(String newTransportName) {
// Publish the name change
Settings.Secure.putStringForUser(mContext.getContentResolver(),
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 b98cb1086680..cf617a523bec 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -323,7 +323,8 @@ public class FullBackupEngine {
private void tearDown() {
if (mPkg != null) {
- backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
+ backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mPkg.applicationInfo, /* allowKill= */ true);
}
}
}
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 dc6709141b25..0ba0d710af38 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -503,7 +503,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
Slog.w(TAG, "adb backup cancel of " + target);
}
if (target != null) {
- mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ target.applicationInfo, /* allowKill= */ true);
}
mOperationStorage.removeOperation(mCurrentOpToken);
}
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 65730c9591a8..990c9416e38d 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -272,8 +272,6 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
- mPackages = backupManagerService.filterUserFacingPackages(mPackages);
-
Set<String> packageNames = Sets.newHashSet();
for (PackageInfo pkgInfo : mPackages) {
packageNames.add(pkgInfo.packageName);
@@ -596,8 +594,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// from the preflight pass. If we got as far as preflight, we now need
// to tear down the target process.
if (mBackupRunner != null) {
- mUserBackupManagerService.tearDownAgentAndKill(
- currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
}
// ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
@@ -609,7 +607,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
BackupObserverUtils
@@ -617,7 +616,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
BackupManager.ERROR_AGENT_FAILURE);
Slog.w(TAG, "Application failure for package: " + packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
BackupObserverUtils
@@ -626,7 +626,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
Slog.w(TAG, "Backup cancelled. package=" + packageName +
", cancelAll=" + mCancelAll);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
BackupObserverUtils
@@ -636,7 +637,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
- mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ currentPackage.applicationInfo, /* allowKill= */ true);
return;
} else {
// Success!
@@ -1005,7 +1007,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mIsCancelled = true;
// Cancel tasks spun off by this task.
mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
- mUserBackupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
+ mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mTarget.applicationInfo, /* allowKill= */ true);
// Free up everyone waiting on this task and its children.
mPreflightLatch.countDown();
mBackupLatch.countDown();
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 82232a653858..689c43a1cc96 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1303,7 +1303,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// For PM metadata (for which applicationInfo is null) there is no agent-bound state.
if (mCurrentPackage.applicationInfo != null) {
mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
- mCurrentPackage.applicationInfo);
+ mCurrentPackage.applicationInfo, /* allowKill= */ false);
}
mAgent = null;
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b59e860f81fe..237ffa3bdb21 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -727,7 +727,8 @@ public class FullRestoreEngine extends RestoreEngine {
latch.await();
}
- mBackupManagerService.tearDownAgentAndKill(app);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ app, /* allowKill= */ true);
} catch (RemoteException e) {
Slog.d(TAG, "Lost app trying to shut down");
}
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 e5c7e5cce757..ec9d340abe45 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -51,7 +51,6 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.EventLog;
@@ -316,8 +315,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
}
- mAcceptSet = backupManagerService.filterUserFacingPackages(mAcceptSet);
-
if (MORE_DEBUG) {
Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
for (PackageInfo info : mAcceptSet) {
@@ -1487,49 +1484,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// If this wasn't the PM pseudopackage, tear down the agent side
if (mCurrentPackage.applicationInfo != null) {
- // unbind and tidy up even on timeout or failure
- try {
- backupManagerService
- .getActivityManager()
- .unbindBackupAgent(mCurrentPackage.applicationInfo);
-
- // The agent was probably running with a stub Application object,
- // which isn't a valid run mode for the main app logic. Shut
- // down the app so that next time it's launched, it gets the
- // usual full initialization. Note that this is only done for
- // full-system restores: when a single app has requested a restore,
- // it is explicitly not killed following that operation.
- //
- // We execute this kill when these conditions hold:
- // 1. it's not a system-uid process,
- // 2. the app did not request its own restore (mTargetPackage == null), and
- // either
- // 3a. the app is a full-data target (TYPE_FULL_STREAM) or
- // b. the app does not state android:killAfterRestore="false" in its manifest
- final int appFlags = mCurrentPackage.applicationInfo.flags;
- final boolean killAfterRestore =
- !UserHandle.isCore(mCurrentPackage.applicationInfo.uid)
- && ((mRestoreDescription.getDataType()
- == RestoreDescription.TYPE_FULL_STREAM)
- || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
- != 0));
-
- if (mTargetPackage == null && killAfterRestore) {
- if (DEBUG) {
- Slog.d(
- TAG,
- "Restore complete, killing host process of "
- + mCurrentPackage.applicationInfo.processName);
- }
- backupManagerService
- .getActivityManager()
- .killApplicationProcess(
- mCurrentPackage.applicationInfo.processName,
- mCurrentPackage.applicationInfo.uid);
- }
- } catch (RemoteException e) {
- // can't happen; we run in the same process as the activity manager
- }
+ // If mTargetPackage is not null it means the app requested its own restore, in which
+ // case we won't allow the app to be killed.
+ backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo, /* allowKill= */ mTargetPackage == null);
}
// The caller is responsible for reestablishing the state machine; our
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 7a2106bb7753..7c8604f06b10 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -240,9 +240,10 @@ class BackupRestoreProcessor {
boolean matchesMacAddress = Objects.equals(
associationInfo.getDeviceMacAddress(),
restored.getDeviceMacAddress());
- boolean matchesTag = !Flags.associationTag()
- || Objects.equals(associationInfo.getTag(), restored.getTag());
- return matchesMacAddress && matchesTag;
+ boolean matchesDeviceId = Flags.associationTag()
+ && (associationInfo.getDeviceId() != null
+ && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
+ return matchesMacAddress || matchesDeviceId;
};
AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 7cba9e0ccca8..418f3a18688b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -55,6 +55,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -318,7 +319,6 @@ public class CompanionDeviceManagerService extends SystemService {
public List<AssociationInfo> getAssociations(String packageName, int userId) {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"get associations");
-
return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
}
@@ -694,14 +694,10 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void setAssociationTag(int associationId, String tag) {
- mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ mAssociationRequestsProcessor.setDeviceId(associationId, deviceId);
}
- @Override
- public void clearAssociationTag(int associationId) {
- setAssociationTag(associationId, null);
- }
@Override
public byte[] getBackupPayload(int userId) {
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 77b17803d434..f2d019bde703 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -38,6 +38,7 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
+import android.companion.DeviceId;
import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Environment;
@@ -131,8 +132,11 @@ import java.util.concurrent.ConcurrentMap;
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634389553216"
- * system_data_sync_flags="0"/>
- *
+ * system_data_sync_flags="0"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* <association
* id="3"
* profile="android.app.role.COMPANION_DEVICE_WATCH"
@@ -143,7 +147,11 @@ import java.util.concurrent.ConcurrentMap;
* revoked="false"
* last_time_connected="1634641160229"
* time_approved="1634641160229"
- * system_data_sync_flags="1"/>
+ * system_data_sync_flags="1"
+ * device_icon="device_icon">
+ * <device_id
+ * custom_device_id="1234"/>
+ * </association>
* </associations>
* </state>
* }</pre>
@@ -160,7 +168,7 @@ public final class AssociationDiskStore {
private static final String XML_TAG_STATE = "state";
private static final String XML_TAG_ASSOCIATIONS = "associations";
private static final String XML_TAG_ASSOCIATION = "association";
- private static final String XML_TAG_TAG = "tag";
+ private static final String XML_TAG_DEVICE_ID = "device_id";
private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
private static final String XML_ATTR_MAX_ID = "max-id";
@@ -177,6 +185,8 @@ public final class AssociationDiskStore {
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
private static final String XML_ATTR_DEVICE_ICON = "device_icon";
+ private static final String XML_ATTR_CUSTOM_DEVICE_ID = "custom_device_id";
+ private static final String XML_ATTR_MAC_ADDRESS_DEVICE_ID = "mac_address_device_id";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -389,16 +399,16 @@ public final class AssociationDiskStore {
requireStartOfTag(parser, XML_TAG_ASSOCIATION);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
- return new AssociationInfo(associationId, userId, appPackage, tag,
+ return new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, null);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, /* deviceIcon */ null,
+ /* deviceId */ null);
}
private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -436,7 +446,6 @@ public final class AssociationDiskStore {
final int associationId = readIntAttribute(parser, XML_ATTR_ID);
final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
- final String tag = readStringAttribute(parser, XML_TAG_TAG);
final MacAddress macAddress = stringToMacAddress(
readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
@@ -451,10 +460,26 @@ public final class AssociationDiskStore {
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
final Icon deviceIcon = byteArrayToIcon(
readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
+ parser.nextTag();
+ final DeviceId deviceId = readDeviceId(parser);
- return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
+ return new AssociationInfo(associationId, userId, appPackage, macAddress, displayName,
profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags, deviceIcon);
+ lastTimeConnected, systemDataSyncFlags, deviceIcon, deviceId);
+ }
+
+ private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
+ throws XmlPullParserException {
+ if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
+ final String customDeviceId = readStringAttribute(
+ parser, XML_ATTR_CUSTOM_DEVICE_ID);
+ final MacAddress macAddress = stringToMacAddress(
+ readStringAttribute(parser, XML_ATTR_MAC_ADDRESS_DEVICE_ID));
+
+ return new DeviceId(customDeviceId, macAddress);
+ }
+
+ return null;
}
private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -475,7 +500,6 @@ public final class AssociationDiskStore {
writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
- writeStringAttribute(serializer, XML_TAG_TAG, a.getTag());
writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
@@ -490,14 +514,33 @@ public final class AssociationDiskStore {
writeByteArrayAttribute(
serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
+ writeDeviceId(serializer, a);
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
+ private static void writeDeviceId(XmlSerializer parent, @NonNull AssociationInfo a)
+ throws IOException {
+ if (a.getDeviceId() != null) {
+ final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_CUSTOM_DEVICE_ID,
+ a.getDeviceId().getCustomId()
+ );
+ writeStringAttribute(
+ serializer,
+ XML_ATTR_MAC_ADDRESS_DEVICE_ID,
+ a.getDeviceId().getMacAddressAsString()
+ );
+ serializer.endTag(null, XML_TAG_DEVICE_ID);
+ }
+ }
+
private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws XmlPullParserException {
if (isStartOfTag(parser, tag)) return;
throw new XmlPullParserException(
- "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
+ "Should be at the start of \"" + tag + "\" tag");
}
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
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 aebd11a7f582..899b302316f9 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -41,6 +41,7 @@ import android.app.PendingIntent;
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceId;
import android.companion.Flags;
import android.companion.IAssociationRequestCallback;
import android.content.ComponentName;
@@ -298,10 +299,10 @@ public class AssociationRequestsProcessor {
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
+ macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
/* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
- deviceIcon);
+ deviceIcon, /* deviceId */ null);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
}
@@ -354,14 +355,14 @@ public class AssociationRequestsProcessor {
}
/**
- * Set association tag.
+ * Set Device id for the association.
*/
- public void setAssociationTag(int associationId, String tag) {
- Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
+ public void setDeviceId(int associationId, DeviceId deviceId) {
+ Slog.i(TAG, "Setting DeviceId=[" + deviceId + "] to id=[" + associationId + "]...");
AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ association = (new AssociationInfo.Builder(association)).setDeviceId(deviceId).build();
mAssociationStore.updateAssociation(association);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 4b9065bc7f72..6069e341521e 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -88,6 +88,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
/** Called when a secure window shows on the virtual display. */
void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo);
+ /** Called when a secure window is no longer shown on the virtual display. */
+ void onSecureWindowHidden(int displayId);
+
/** Returns true when an intent should be intercepted */
boolean shouldInterceptIntent(@NonNull Intent intent);
}
@@ -123,6 +126,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
private boolean mIsMirrorDisplay = false;
private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1);
+ // Used for detecting changes in the window flags.
+ private int mCurrentWindowFlags = 0;
+
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
private final ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -371,12 +377,19 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
int displayId = waitAndGetDisplayId();
- // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
- // aware that the virtual display has a secure window on top.
- if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) {
+ if (displayId != INVALID_DISPLAY) {
+ // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+ // aware that the virtual display has a secure window on top.
// Post callback on the main thread, so it doesn't block activity launching.
- mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+ if ((windowFlags & FLAG_SECURE) != 0 && (mCurrentWindowFlags & FLAG_SECURE) == 0) {
+ mHandler.post(
+ () -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+ }
+ if ((windowFlags & FLAG_SECURE) == 0 && (mCurrentWindowFlags & FLAG_SECURE) != 0) {
+ mHandler.post(() -> mActivityListener.onSecureWindowHidden(displayId));
+ }
}
+ mCurrentWindowFlags = windowFlags;
if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
activityInfo.packageName,
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index d4beb019e049..a1d621d8dd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -44,6 +44,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
@@ -155,6 +156,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
+ private static final List<String> DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS = List.of(
+ AssociationRequest.DEVICE_PROFILE_APP_STREAMING);
+
/**
* Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
*/
@@ -308,6 +312,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
+ @Override
+ public void onSecureWindowHidden(int displayId) {
+ if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ try {
+ mActivityListener.onSecureWindowHidden(displayId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+ }
+ }
+ }
+
/**
* Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true
* if the intent matches any filter notifying the DisplayPolicyController to abort the
@@ -1348,6 +1363,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return hasCustomAudioInputSupportInternal();
}
+ @Override
+ public boolean canCreateMirrorDisplays() {
+ return DEVICE_PROFILES_ALLOWING_MIRROR_DISPLAYS.contains(getDeviceProfile());
+ }
+
private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index aea16b08df49..9e645595708c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,6 +127,7 @@ java_library_static {
"android.hardware.power-java_shared",
"latest_android_hardware_broadcastradio_java_static",
"services_crashrecovery_stubs_conditionally",
+ "ondeviceintelligence_conditionally",
],
srcs: [
":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -157,6 +158,7 @@ java_library_static {
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V3-java",
+ "androidx.annotation_annotation",
"app-compat-annotations",
"art_exported_aconfig_flags_lib",
"framework-tethering.stubs.module_lib",
@@ -178,6 +180,7 @@ java_library_static {
static_libs: [
"android.frameworks.vibrator-V1-java", // AIDL
+ "android.frameworks.devicestate-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
"android.hardware.boot-V1.0-java", // HIDL
@@ -188,7 +191,7 @@ java_library_static {
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
- "android.hardware.health-V3-java", // AIDL
+ "android.hardware.health-V4-java", // AIDL
"android.hardware.health-translate-java",
"android.hardware.light-V1-java",
"android.hardware.security.authgraph-V1-java",
@@ -220,6 +223,7 @@ java_library_static {
"securebox",
"apache-commons-math",
"battery_saver_flag_lib",
+ "guava",
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
@@ -238,11 +242,11 @@ java_library_static {
"connectivity_flags_lib",
"device_config_service_flags_java",
"dreams_flags_lib",
- "aconfig_flags_java",
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
"profiling_flags_lib",
+ "android.adpf.sessionmanager_aidl-java",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 43774bbc51ca..b0dae6a1f306 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -90,6 +90,7 @@ public abstract class PackageManagerInternal {
*/
public static final int RESOLVE_NON_RESOLVER_ONLY = 0x00000002;
+ @Deprecated
@IntDef(value = {
INTEGRITY_VERIFICATION_ALLOW,
INTEGRITY_VERIFICATION_REJECT,
@@ -97,18 +98,10 @@ public abstract class PackageManagerInternal {
@Retention(RetentionPolicy.SOURCE)
public @interface IntegrityVerificationResult {}
- /**
- * Used as the {@code verificationCode} argument for
- * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
- * integrity component allows the install to proceed.
- */
+ @Deprecated
public static final int INTEGRITY_VERIFICATION_ALLOW = 1;
- /**
- * Used as the {@code verificationCode} argument for
- * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
- * integrity component does not allow install to proceed.
- */
+ @Deprecated
public static final int INTEGRITY_VERIFICATION_REJECT = 0;
/**
@@ -1131,17 +1124,13 @@ public abstract class PackageManagerInternal {
public abstract boolean isPermissionUpgradeNeeded(@UserIdInt int userId);
/**
- * Allows the integrity component to respond to the
- * {@link Intent#ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
- * broadcast} to respond to the package manager. The response must include
- * the {@code verificationCode} which is one of
- * {@link #INTEGRITY_VERIFICATION_ALLOW} and {@link #INTEGRITY_VERIFICATION_REJECT}.
+ * Used to allow the integrity component to respond to the
+ * ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
+ * broadcast to respond to the package manager.
*
- * @param verificationId pending package identifier as passed via the
- * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
- * @param verificationResult either {@link #INTEGRITY_VERIFICATION_ALLOW}
- * or {@link #INTEGRITY_VERIFICATION_REJECT}.
+ * Deprecated.
*/
+ @Deprecated
public abstract void setIntegrityVerificationResult(int verificationId,
@IntegrityVerificationResult int verificationResult);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 0286f7b0b73d..36dff89d9d61 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -101,6 +101,9 @@ import java.util.Map;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+
/**
* @hide
*/
@@ -138,6 +141,10 @@ public class BinaryTransparencyService extends SystemService {
static final int MBA_STATUS_NEW_INSTALL = 3;
// used for indicating newly installed MBAs that are updated (but unused currently)
static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+ // used for indicating preloaded MBAs that are downgraded
+ static final int MBA_STATUS_DOWNGRADED_PRELOADED = 5;
+ // used for indicating MBAs that are uninstalled
+ static final int MBA_STATUS_UNINSTALLED = 6;
@VisibleForTesting
static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
@@ -202,7 +209,9 @@ public class BinaryTransparencyService extends SystemService {
* @param mbaStatus Assign this value of MBA status to the returned elements.
* @return a @{@code List<IBinaryTransparencyService.AppInfo>}
*/
- private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ @VisibleForTesting
+ @NonNull
+ List<IBinaryTransparencyService.AppInfo> collectAppInfo(
PackageState packageState, int mbaStatus) {
// compute content digest
if (DEBUG) {
@@ -336,27 +345,28 @@ public class BinaryTransparencyService extends SystemService {
+ " packages after considering APEXs.");
}
- // proceed with all preloaded apps
- List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
- collectAllUpdatedPreloadInfo(packagesMeasured);
- for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
- packagesMeasured.putBoolean(appInfo.packageName, true);
- writeAppInfoToLog(appInfo);
- }
- if (DEBUG) {
- Slog.d(TAG, "Measured " + packagesMeasured.size()
- + " packages after considering preloads");
- }
-
- if (!android.app.Flags.backgroundInstallControlCallbackApi()
- && CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
- // lastly measure all newly installed MBAs
- List<IBinaryTransparencyService.AppInfo> allMbaInfo =
- collectAllSilentInstalledMbaInfo(packagesMeasured);
- for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+ if (!android.app.Flags.backgroundInstallControlCallbackApi()) {
+ // proceed with all preloaded apps
+ List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
+ collectAllUpdatedPreloadInfo(packagesMeasured);
+ for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
}
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering preloads");
+ }
+
+ if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+ // lastly measure all newly installed MBAs
+ List<IBinaryTransparencyService.AppInfo> allMbaInfo =
+ collectAllSilentInstalledMbaInfo(packagesMeasured);
+ for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+ packagesMeasured.putBoolean(appInfo.packageName, true);
+ writeAppInfoToLog(appInfo);
+ }
+ }
}
long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
digestAllPackagesLatency.logSample(timeSpentMeasuring);
@@ -466,7 +476,8 @@ public class BinaryTransparencyService extends SystemService {
apexInfo.signerDigests);
}
- private void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
+ @VisibleForTesting
+ void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
// Must order by the proto's field number.
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
appInfo.packageName,
@@ -1165,41 +1176,86 @@ public class BinaryTransparencyService extends SystemService {
* TODO: Add a host test for testing registration and callback of BicCallbackHandler
* b/380002484
*/
+ @VisibleForTesting
static class BicCallbackHandler extends IRemoteCallback.Stub {
- private static final String BIC_CALLBACK_HANDLER_TAG =
- "BTS.BicCallbackHandler";
- private final BinaryTransparencyServiceImpl mServiceImpl;
- static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+ private static final String BIC_CALLBACK_HANDLER_TAG = TAG + ".BicCallbackHandler";
+
+ private static final int INSTALL_EVENT_TYPE_UNSET = -1;
+
+ private final IBicAppInfoHelper mBicAppInfoHelper;
- BicCallbackHandler(BinaryTransparencyServiceImpl impl) {
- mServiceImpl = impl;
+ @VisibleForTesting
+ BicCallbackHandler(IBicAppInfoHelper bicAppInfoHelper) {
+ mBicAppInfoHelper = bicAppInfoHelper;
}
@Override
public void sendResult(Bundle data) {
- String packageName = data.getString(FLAGGED_PACKAGE_NAME_KEY);
- if (packageName == null) return;
- if (DEBUG) {
- Slog.d(BIC_CALLBACK_HANDLER_TAG, "background install event detected for "
- + packageName);
- }
-
- PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
- .getPackageStateInternal(packageName);
- if (packageState == null) {
- Slog.w(TAG, "Package state is unavailable, ignoring the package "
- + packageName);
- return;
- }
- if (packageState.isUpdatedSystemApp()) {
+ String packageName = data.getString(
+ BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY);
+ int installType = data.getInt(
+ BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ INSTALL_EVENT_TYPE_UNSET);
+ if (packageName == null || installType == INSTALL_EVENT_TYPE_UNSET) {
+ Slog.w(BIC_CALLBACK_HANDLER_TAG, "Package name or install type is "
+ + "unavailable, ignoring event");
return;
}
- List<IBinaryTransparencyService.AppInfo> mbaInfo = mServiceImpl.collectAppInfo(
- packageState, MBA_STATUS_NEW_INSTALL);
- for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
- mServiceImpl.writeAppInfoToLog(appInfo);
+ Slog.d(BIC_CALLBACK_HANDLER_TAG, "Detected new bic event for: " + packageName);
+ if (installType == BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL) {
+ PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
+ .getPackageStateInternal(packageName);
+ if (packageState == null) {
+ Slog.w(TAG, "Package state is unavailable, ignoring the package "
+ + packageName);
+ return;
+ }
+ int mbaStatus = MBA_STATUS_NEW_INSTALL;
+ if (packageState.isUpdatedSystemApp()) {
+ mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
+ }
+ List<IBinaryTransparencyService.AppInfo> mbaInfo = mBicAppInfoHelper.collectAppInfo(
+ packageState, mbaStatus);
+ for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
+ mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+ }
+ } else if (installType
+ == BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL) {
+ IBinaryTransparencyService.AppInfo appInfo
+ = new IBinaryTransparencyService.AppInfo();
+ // since app is already uninstalled we won't be able to retrieve additional
+ // info on it.
+ appInfo.packageName = packageName;
+ appInfo.mbaStatus = MBA_STATUS_UNINSTALLED;
+ mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+ } else {
+ Slog.w(BIC_CALLBACK_HANDLER_TAG, "Unsupported BIC event: " + installType);
}
}
+
+ /**
+ * A wrapper of interface for{@link FrameworkStatsLog and ApkDigests}
+ * for easier testing
+ */
+ @VisibleForTesting
+ public interface IBicAppInfoHelper {
+
+ /**
+ * A wrapper of {@link FrameworkStatsLog}
+ *
+ * @param appInfo The app info of the changed MBA to be logged
+ */
+ public void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo);
+
+ /**
+ * A wrapper of {@link BinaryTransparencyServiceImpl}
+ *
+ * @param packageState The packageState provided retrieved from PackageManagerInternal
+ * @param mbaStatus The MBA status of the package
+ */
+ public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus);
+ }
};
/**
@@ -1596,7 +1652,21 @@ public class BinaryTransparencyService extends SystemService {
}
try {
iBics.registerBackgroundInstallCallback(
- new BicCallbackHandler(mServiceImpl));
+ new BicCallbackHandler(
+ new BicCallbackHandler.IBicAppInfoHelper() {
+ @Override
+ public void writeAppInfoToLog(
+ IBinaryTransparencyService.AppInfo appInfo) {
+ mServiceImpl.writeAppInfoToLog(appInfo);
+ }
+
+ @Override
+ public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus) {
+ return mServiceImpl.collectAppInfo(packageState, mbaStatus);
+ }
+ }
+ ));
} catch (RemoteException e) {
Slog.e(TAG, "Failed to register BackgroundInstallControl callback.");
}
@@ -1643,8 +1713,12 @@ public class BinaryTransparencyService extends SystemService {
}
String packageName = data.getSchemeSpecificPart();
- // now we've got to check what package is this
- if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) {
+
+ boolean shouldMeasureMba =
+ !android.app.Flags.backgroundInstallControlCallbackApi()
+ && isPackagePreloaded(packageName);
+
+ if (shouldMeasureMba || isPackageAnApex(packageName)) {
Slog.d(TAG, packageName + " was updated. Scheduling measurement...");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index fb527c104946..2412b01ea8e2 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@ package com.android.server;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -35,6 +36,7 @@ import android.provider.Settings;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -259,11 +261,19 @@ final class DockObserver extends SystemService {
+ mReportedDockState);
final int previousDockState = mPreviousDockState;
mPreviousDockState = mReportedDockState;
- // Skip the dock intent if not yet provisioned.
+
final ContentResolver cr = getContext().getContentResolver();
- if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
- Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
- return;
+
+ /// If the allow dock rotation before provision is enabled then we allow rotation.
+ final Resources r = getContext().getResources();
+ final boolean allowDockBeforeProvision =
+ r.getBoolean(R.bool.config_allowDockBeforeProvision);
+ if (!allowDockBeforeProvision) {
+ // Skip the dock intent if not yet provisioned.
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
+ Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
+ return;
+ }
}
// Pack up the values and broadcast them to everyone
diff --git a/services/core/java/com/android/server/SecurityStateManagerService.java b/services/core/java/com/android/server/SecurityStateManagerService.java
index 98039be20897..fe21fbda7130 100644
--- a/services/core/java/com/android/server/SecurityStateManagerService.java
+++ b/services/core/java/com/android/server/SecurityStateManagerService.java
@@ -22,6 +22,7 @@ import static android.os.SecurityStateManager.KEY_VENDOR_SPL;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ISecurityStateManager;
@@ -56,6 +57,15 @@ public class SecurityStateManagerService extends ISecurityStateManager.Stub {
@Override
public Bundle getGlobalSecurityState() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getGlobalSecurityStateInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private Bundle getGlobalSecurityStateInternal() {
Bundle globalSecurityState = new Bundle();
globalSecurityState.putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH);
globalSecurityState.putString(KEY_VENDOR_SPL,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index db932a78b158..bd7a0ac55117 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2178,7 +2178,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
}
boolean isRoaming = telephonyDisplayInfo.isRoaming();
- return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
+ boolean isNtn = telephonyDisplayInfo.isNtn();
+ boolean isSatelliteConstrainedData =
+ telephonyDisplayInfo.isSatelliteConstrainedData();
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming,
+ isNtn, isSatelliteConstrainedData);
}
public void notifyCallForwardingChanged(boolean cfi) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index d13dd2f2e1fc..896c9b8d0932 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -885,8 +885,6 @@ final class UiModeManagerService extends SystemService {
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode.set(mode);
- //deactivates AttentionMode if user toggles DarkTheme
- mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
@@ -1009,15 +1007,16 @@ final class UiModeManagerService extends SystemService {
@Override
public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
- return setNightModeActivatedForModeInternal(modeNightCustomType, active);
+ return setNightModeActivatedForModeInternal(modeNightCustomType, active, false);
}
@Override
public boolean setNightModeActivated(boolean active) {
- return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
+ return setNightModeActivatedForModeInternal(mNightModeCustomType, active, true);
}
- private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
+ private boolean setNightModeActivatedForModeInternal(int modeCustomType,
+ boolean active, boolean isUserInteraction) {
if (getContext().checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1053,13 +1052,16 @@ final class UiModeManagerService extends SystemService {
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
- } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
- && active) {
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO && active) {
mNightMode.set(UiModeManager.MODE_NIGHT_YES);
- } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
- && !active) {
+ } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES && !active) {
mNightMode.set(UiModeManager.MODE_NIGHT_NO);
}
+
+ if (isUserInteraction) {
+ // deactivates AttentionMode if user toggles DarkTheme
+ mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
+ }
updateConfigurationLocked();
applyConfigurationExternallyLocked();
persistNightMode(mCurrentUser);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3ce645158fe4..719928b8e582 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2846,6 +2846,14 @@ public class AccountManagerService
: AccountsDb.DEBUG_ACTION_SET_PASSWORD;
logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts,
callingUid);
+
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ TextUtils.isEmpty(password)
+ ? FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_REMOVED
+ : FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_CHANGED);
}
} finally {
accounts.accountsDb.endTransaction();
@@ -2912,7 +2920,7 @@ public class AccountManagerService
if (!accountExistsCache(accounts, account)) {
return;
}
- setUserdataInternal(accounts, account, key, value);
+ setUserdataInternal(accounts, account, key, value, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2932,7 +2940,7 @@ public class AccountManagerService
}
private void setUserdataInternal(UserAccounts accounts, Account account, String key,
- String value) {
+ String value, int callingUid) {
synchronized (accounts.dbLock) {
accounts.accountsDb.beginTransaction();
try {
@@ -2958,6 +2966,11 @@ public class AccountManagerService
AccountManager.invalidateLocalAccountUserDataCaches();
}
}
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+ account.type,
+ callingUid,
+ FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__USER_DATA_CHANGED);
}
private void onResult(IAccountManagerResponse response, Bundle result) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 71cbc10074d6..60516c39ffa7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4604,7 +4604,7 @@ public final class ActiveServices {
return true;
}
- void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
+ void unbindFinishedLocked(ServiceRecord r, Intent intent) {
final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (r != null) {
@@ -5845,7 +5845,7 @@ public final class ActiveServices {
if (r.inSharedIsolatedProcess) {
app = mAm.mProcessList.getSharedIsolatedProcess(procName, r.appInfo.uid,
r.appInfo.packageName);
- if (app != null) {
+ if (app != null && !app.isKilled()) {
final IApplicationThread thread = app.getThread();
final int pid = app.getPid();
final UidRecord uidRecord = app.getUidRecord();
@@ -5870,6 +5870,8 @@ public final class ActiveServices {
// If a dead object exception was thrown -- fall through to
// restart the application.
}
+ } else {
+ app = null;
}
} else {
// If this service runs in an isolated process, then each time
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 053ec824e1a7..cd929c1883d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -132,7 +132,7 @@ import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.security.Flags.preventIntentRedirect;
import static android.security.Flags.preventIntentRedirectCollectNestedKeysOnServerIfNotCollected;
-import static android.security.Flags.preventIntentRedirectShowToast;
+import static android.security.Flags.preventIntentRedirectShowToastIfNestedKeysNotCollectedRW;
import static android.security.Flags.preventIntentRedirectThrowExceptionIfNestedKeysNotCollected;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -192,6 +192,7 @@ import static com.android.systemui.shared.Flags.enableHomeDelay;
import android.Manifest;
import android.Manifest.permission;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionMethod;
@@ -360,6 +361,8 @@ import android.os.WorkSource;
import android.os.incremental.IIncrementalService;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalMetrics;
+import android.os.instrumentation.IOffsetCallback;
+import android.os.instrumentation.MethodDescriptor;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
@@ -508,6 +511,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -2780,9 +2784,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mIsolatedAppBindArgs = new ArrayMap<>(1);
// See b/79378449 about the following exemption.
addServiceToMap(mIsolatedAppBindArgs, "package");
- if (!android.server.Flags.removeJavaServiceManagerCache()) {
- addServiceToMap(mIsolatedAppBindArgs, "permissionmgr");
- }
+ addServiceToMap(mIsolatedAppBindArgs, "permissionmgr");
}
return mIsolatedAppBindArgs;
}
@@ -2793,38 +2795,27 @@ public class ActivityManagerService extends IActivityManager.Stub
// Add common services.
// IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
// Enable the check in ApplicationThread.bindApplication() to make sure.
-
- // Removing User Service and App Ops Service from cache breaks boot for auto.
- // Removing permissionmgr breaks tests for Android Auto due to SELinux restrictions.
- // TODO: fix SELinux restrictions and remove caching for Android Auto.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
- || !android.server.Flags.removeJavaServiceManagerCache()) {
- addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE);
- addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE);
- addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE);
- addServiceToMap(mAppBindArgs, Context.CONNECTIVITY_SERVICE);
- addServiceToMap(mAppBindArgs, Context.ACCESSIBILITY_SERVICE);
- addServiceToMap(mAppBindArgs, Context.INPUT_METHOD_SERVICE);
- addServiceToMap(mAppBindArgs, Context.INPUT_SERVICE);
- addServiceToMap(mAppBindArgs, "graphicsstats");
- addServiceToMap(mAppBindArgs, "content");
- addServiceToMap(mAppBindArgs, Context.JOB_SCHEDULER_SERVICE);
- addServiceToMap(mAppBindArgs, Context.NOTIFICATION_SERVICE);
- addServiceToMap(mAppBindArgs, Context.VIBRATOR_SERVICE);
- addServiceToMap(mAppBindArgs, Context.ACCOUNT_SERVICE);
- addServiceToMap(mAppBindArgs, Context.POWER_SERVICE);
- addServiceToMap(mAppBindArgs, "mount");
- addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE);
- }
- // See b/79378449
- // Getting the window service and package service binder from servicemanager
- // is blocked for Apps. However they are necessary for apps.
- // TODO: remove exception
addServiceToMap(mAppBindArgs, "package");
- addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
- addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
addServiceToMap(mAppBindArgs, "permissionmgr");
+ addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.CONNECTIVITY_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.ACCESSIBILITY_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.INPUT_METHOD_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.INPUT_SERVICE);
+ addServiceToMap(mAppBindArgs, "graphicsstats");
addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
+ addServiceToMap(mAppBindArgs, "content");
+ addServiceToMap(mAppBindArgs, Context.JOB_SCHEDULER_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.NOTIFICATION_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.VIBRATOR_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.ACCOUNT_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.POWER_SERVICE);
+ addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
+ addServiceToMap(mAppBindArgs, "mount");
+ addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE);
}
return mAppBindArgs;
}
@@ -7552,7 +7543,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo,
- ApplicationInfo sdkSandboxClientApp) {
+ ApplicationInfo sdkSandboxClientApp, int profileType) {
synchronized (mAppProfiler.mProfilerLock) {
if (!Build.IS_DEBUGGABLE) {
boolean isAppDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
@@ -7568,7 +7559,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ "and not profileable by shell: " + app.packageName);
}
}
- mAppProfiler.setProfileAppLPf(processName, profilerInfo);
+ mAppProfiler.setProfileAppLPf(processName, profilerInfo, profileType);
}
}
@@ -13976,14 +13967,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- public void unbindFinished(IBinder token, Intent intent, boolean doRebind) {
+ public void unbindFinished(IBinder token, Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
synchronized(this) {
- mServices.unbindFinishedLocked((ServiceRecord)token, intent, doRebind);
+ mServices.unbindFinishedLocked((ServiceRecord)token, intent);
}
}
@@ -15846,7 +15837,8 @@ public class ActivityManagerService extends IActivityManager.Stub
+ android.Manifest.permission.SET_ACTIVITY_WATCHER);
}
- if (start && (profilerInfo == null || profilerInfo.profileFd == null)) {
+ if (start && profileType == ProfilerInfo.PROFILE_TYPE_REGULAR
+ && (profilerInfo == null || profilerInfo.profileFd == null)) {
throw new IllegalArgumentException("null profile info or fd");
}
@@ -17489,7 +17481,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (profilerInfo != null) {
- setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo, null);
+ // We only support normal method tracing along with app startup for now.
+ setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo,
+ null, /*profileType= */ ProfilerInfo.PROFILE_TYPE_REGULAR);
}
wmLock.notify();
}
@@ -18051,6 +18045,26 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public void getExecutableMethodFileOffsets(@NonNull String processName,
+ int pid, int uid, @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback callback) {
+ final IApplicationThread thread;
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord record = mProcessList.getProcessRecordLocked(processName, uid);
+ if (record == null || record.getPid() != pid) {
+ throw new NoSuchElementException();
+ }
+ thread = record.getThread();
+ }
+ try {
+ thread.getExecutableMethodFileOffsets(methodDescriptor, callback);
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "IApplicationThread.getExecutableMethodFileOffsets failed", e);
+ }
+ }
+
+ @Override
public void addCreatorToken(Intent intent, String creatorPackage) {
ActivityManagerService.this.addCreatorToken(intent, creatorPackage);
}
@@ -19228,6 +19242,11 @@ public class ActivityManagerService extends IActivityManager.Stub
return mKeyFields.mCreatorPackage;
}
+ @VisibleForTesting
+ public @NonNull Key getKeyFields() {
+ return mKeyFields;
+ }
+
public static boolean isValid(@NonNull Intent intent) {
IBinder binder = intent.getCreatorToken();
IntentCreatorToken token = null;
@@ -19271,9 +19290,13 @@ public class ActivityManagerService extends IActivityManager.Stub
this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
ClipData clipData = intent.getClipData();
if (clipData != null) {
- this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
- for (int i = 0; i < clipData.getItemCount(); i++) {
- this.mClipDataUris.add(clipData.getItemAt(i).getUri());
+ clipData = clipData.cloneOnlyUriItems();
+ if (clipData != null) {
+ List<Uri> clipDataUris = new ArrayList<>();
+ clipData.collectUris(clipDataUris);
+ if (!clipDataUris.isEmpty()) {
+ this.mClipDataUris = clipDataUris;
+ }
}
}
}
@@ -19321,12 +19344,13 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!preventIntentRedirect()) return;
if (intent == null) return;
- if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0) {
+ if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
+ && intent.getExtras() != null && intent.getExtras().hasIntent()) {
Slog.wtf(TAG,
"[IntentRedirect] The intent does not have its nested keys collected as a "
+ "preparation for creating intent creator tokens. Intent: "
+ intent + "; creatorPackage: " + creatorPackage);
- if (preventIntentRedirectShowToast()) {
+ if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
UiThread.getHandler().post(
() -> Toast.makeText(mContext,
"Nested keys not collected. go/report-bug-intentRedir to report a"
@@ -19375,11 +19399,34 @@ public class ActivityManagerService extends IActivityManager.Stub
String creatorPackage) {
if (IntentCreatorToken.isValid(intent)) return null;
IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
+ return createOrGetIntentCreatorToken(intent, key);
+ }
+
+ /**
+ * @hide
+ */
+ @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
+ public IBinder refreshIntentCreatorToken(Intent intent) {
+ refreshIntentCreatorToken_enforcePermission();
+ IBinder binder = intent.getCreatorToken();
+ if (binder instanceof IntentCreatorToken) {
+ IntentCreatorToken token = (IntentCreatorToken) binder;
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(),
+ token.getCreatorPackage(), intent);
+ return createOrGetIntentCreatorToken(intent, key);
+
+ } else {
+ return null;
+ }
+ }
+
+ private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent,
+ IntentCreatorToken.Key key) {
IntentCreatorToken token;
synchronized (sIntentCreatorTokenCache) {
WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
if (ref == null || ref.get() == null) {
- token = new IntentCreatorToken(creatorUid, creatorPackage, intent);
+ token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent);
sIntentCreatorTokenCache.put(key, token.mRef);
} else {
token = ref.get();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 37d058b06b99..9a63546bf5a7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1155,7 +1155,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
String profileFile = null;
boolean start = false;
int userId = UserHandle.USER_CURRENT;
- int profileType = 0;
+ int profileType = ProfilerInfo.PROFILE_TYPE_REGULAR;
mSamplingInterval = 0;
mStreaming = false;
mClockType = ProfilerInfo.CLOCK_TYPE_DEFAULT;
@@ -1197,6 +1197,18 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
process = getNextArgRequired();
+ } else if ("lowoverhead".equals(cmd)) {
+ // This is an experimental low overhead profiling.
+ profileType = ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD;
+ cmd = getNextArgRequired();
+ if ("start".equals(cmd)) {
+ start = true;
+ } else if ("stop".equals(cmd)) {
+ start = false;
+ } else {
+ throw new IllegalArgumentException("Profile command not valid");
+ }
+ process = getNextArgRequired();
} else {
// Compatibility with old syntax: process is specified first.
process = cmd;
@@ -1216,7 +1228,12 @@ final class ActivityManagerShellCommand extends ShellCommand {
ParcelFileDescriptor fd = null;
ProfilerInfo profilerInfo = null;
- if (start) {
+ // For regular method tracing profileFile should be provided with the start command. For
+ // low overhead method tracing the profileFile is optional and provided with the stop
+ // command.
+ if ((start && profileType == ProfilerInfo.PROFILE_TYPE_REGULAR)
+ || (profileType == ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD
+ && !start && getRemainingArgsCount() > 0)) {
profileFile = getNextArgRequired();
fd = openFileForSystem(profileFile, "w");
if (fd == null) {
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 4f2d69e4bb72..6b24df4a1fa8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1992,7 +1992,7 @@ public class AppProfiler {
}
@GuardedBy("mProfilerLock")
- private void stopProfilerLPf(ProcessRecord proc, int profileType) {
+ private void stopProfilerLPf(ProcessRecord proc, ProfilerInfo profilerInfo, int profileType) {
if (proc == null || proc == mProfileData.getProfileProc()) {
proc = mProfileData.getProfileProc();
profileType = mProfileType;
@@ -2006,7 +2006,7 @@ public class AppProfiler {
return;
}
try {
- thread.profilerControl(false, null, profileType);
+ thread.profilerControl(false, profilerInfo, profileType);
} catch (RemoteException e) {
throw new IllegalStateException("Process disappeared");
}
@@ -2041,41 +2041,58 @@ public class AppProfiler {
ProfilerInfo profilerInfo, int profileType) {
try {
if (start) {
- stopProfilerLPf(null, 0);
+ boolean needsFile = (profileType == ProfilerInfo.PROFILE_TYPE_REGULAR);
+ stopProfilerLPf(null, null, 0);
mService.setProfileApp(proc.info, proc.processName, profilerInfo,
- proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null);
+ proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null, profileType);
mProfileData.setProfileProc(proc);
mProfileType = profileType;
- ParcelFileDescriptor fd = profilerInfo.profileFd;
- try {
- fd = fd.dup();
- } catch (IOException e) {
- fd = null;
+
+ ParcelFileDescriptor fd = null;
+ if (needsFile) {
+ fd = profilerInfo.profileFd;
+ try {
+ fd = fd.dup();
+ } catch (IOException e) {
+ fd = null;
+ }
+ profilerInfo.profileFd = fd;
}
- profilerInfo.profileFd = fd;
+
proc.mProfile.getThread().profilerControl(start, profilerInfo, profileType);
- fd = null;
- try {
- mProfileData.getProfilerInfo().profileFd.close();
- } catch (IOException e) {
- }
- mProfileData.getProfilerInfo().profileFd = null;
-
- if (proc.getPid() == mService.MY_PID) {
- // When profiling the system server itself, avoid closing the file
- // descriptor, as profilerControl will not create a copy.
- // Note: it is also not correct to just set profileFd to null, as the
- // whole ProfilerInfo instance is passed down!
- profilerInfo = null;
+
+ if (needsFile) {
+ fd = null;
+ try {
+ mProfileData.getProfilerInfo().profileFd.close();
+ } catch (IOException e) {
+ }
+ mProfileData.getProfilerInfo().profileFd = null;
+
+ if (proc.getPid() == mService.MY_PID) {
+ // When profiling the system server itself, avoid closing the file
+ // descriptor, as profilerControl will not create a copy.
+ // Note: it is also not correct to just set profileFd to null, as the
+ // whole ProfilerInfo instance is passed down!
+ profilerInfo = null;
+ }
}
} else {
- stopProfilerLPf(proc, profileType);
+ boolean mayNeedFile = (profileType == ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD);
if (profilerInfo != null && profilerInfo.profileFd != null) {
+ ParcelFileDescriptor fd = profilerInfo.profileFd;
try {
- profilerInfo.profileFd.close();
+ if (mayNeedFile) {
+ fd = fd.dup();
+ } else {
+ fd.close();
+ }
} catch (IOException e) {
+ fd = null;
}
+ profilerInfo.profileFd = fd;
}
+ stopProfilerLPf(proc, profilerInfo, profileType);
}
return true;
@@ -2092,7 +2109,7 @@ public class AppProfiler {
}
@GuardedBy("mProfilerLock")
- void setProfileAppLPf(String processName, ProfilerInfo profilerInfo) {
+ void setProfileAppLPf(String processName, ProfilerInfo profilerInfo, int profileType) {
mProfileData.setProfileApp(processName);
if (mProfileData.getProfilerInfo() != null) {
@@ -2103,8 +2120,10 @@ public class AppProfiler {
}
}
}
- mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo));
- mProfileType = 0;
+ if (profilerInfo != null) {
+ mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo));
+ }
+ mProfileType = profileType;
}
@GuardedBy("mProfilerLock")
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 400ebfde1741..aea24d978bee 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1101,6 +1101,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
/** StatsPullAtomCallback for pulling BatteryUsageStats data. */
private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+ private static final long BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE =
+ TimeUnit.HOURS.toMillis(2);
+
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
final BatteryUsageStats bus;
@@ -1168,7 +1171,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.setMinConsumedPowerThreshold(minConsumedPowerThreshold);
if (isBatteryUsageStatsAccumulationSupported()) {
- query.accumulated();
+ query.accumulated()
+ .setMaxStatsAgeMs(BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE);
}
bus = getBatteryUsageStats(List.of(query.build())).get(0);
@@ -1251,12 +1255,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
private static float clampPowerMah(double powerMah, String consumer) {
- float resultPowerMah = 0;
- if (powerMah <= Float.MAX_VALUE && powerMah >= Float.MIN_VALUE) {
- resultPowerMah = (float) powerMah;
- } else {
- // Handle overflow appropriately
- Slog.wtfStack(TAG, consumer + " reported powerMah float overflow: " + powerMah);
+ float resultPowerMah = Double.valueOf(powerMah).floatValue();
+ if (Float.isInfinite(resultPowerMah)) {
+ resultPowerMah = 0;
+ Slog.d(TAG, consumer + " reported powerMah float overflow : " + powerMah);
}
return resultPowerMah;
}
@@ -1361,11 +1363,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final String powerComponentName = batteryConsumer.getPowerComponentName(componentId);
final double consumedPowerMah = batteryConsumer.getConsumedPower(key);
- float powerMah =
+ final float powerMah =
clampPowerMah(
- consumedPowerMah, "uidConsumer-" + uid + "-" + powerComponentName);
+ consumedPowerMah, "uid-" + uid + "-" + powerComponentName);
final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
-
if (powerMah == 0 && powerComponentDurationMillis == 0) {
return true;
}
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index c6cb67f4efa8..354f281551b2 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,6 +57,7 @@ import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
+import android.app.BroadcastStickyCache;
import android.app.IApplicationThread;
import android.app.compat.CompatChanges;
import android.appwidget.AppWidgetManager;
@@ -183,6 +184,13 @@ class BroadcastController {
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
/**
+ * If {@code false} invalidate the list of {@link android.os.IpcDataCache} present inside the
+ * {@link BroadcastStickyCache} class.
+ * The invalidation is required to start caching of the sticky broadcast in the client side.
+ */
+ private volatile boolean mAreStickyCachesInvalidated = false;
+
+ /**
* Resolver for broadcast intents to registered receivers.
* Holds BroadcastFilter (subclass of IntentFilter).
*/
@@ -288,6 +296,11 @@ class BroadcastController {
IIntentReceiver receiver, IntentFilter filter, String permission,
int userId, int flags) {
mService.enforceNotIsolatedCaller("registerReceiver");
+
+ if (!mAreStickyCachesInvalidated) {
+ BroadcastStickyCache.invalidateAllCaches();
+ mAreStickyCachesInvalidated = true;
+ }
ArrayList<StickyBroadcast> stickyBroadcasts = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps =
@@ -700,6 +713,7 @@ class BroadcastController {
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
mService.enforceNotIsolatedCaller("broadcastIntent");
+ final int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -722,7 +736,7 @@ class BroadcastController {
final long origId = Binder.clearCallingIdentity();
try {
- return broadcastIntentLocked(callerApp,
+ result = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -733,6 +747,11 @@ class BroadcastController {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
+
+ if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+ BroadcastStickyCache.invalidateCache(intent.getAction());
+ }
+ return result;
}
// Not the binder call surface
@@ -743,6 +762,7 @@ class BroadcastController {
boolean serialized, boolean sticky, int userId,
BackgroundStartPrivileges backgroundStartPrivileges,
@Nullable int[] broadcastAllowList) {
+ final int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -750,7 +770,7 @@ class BroadcastController {
String[] requiredPermissions = requiredPermission == null ? null
: new String[] {requiredPermission};
try {
- return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
resultToApp, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
uid, realCallingUid, realCallingPid, userId,
@@ -760,6 +780,11 @@ class BroadcastController {
Binder.restoreCallingIdentity(origId);
}
}
+
+ if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+ BroadcastStickyCache.invalidateCache(intent.getAction());
+ }
+ return result;
}
@GuardedBy("mService")
@@ -1458,6 +1483,7 @@ class BroadcastController {
list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
callingUid, callerAppProcessState, resolvedType));
}
+ BroadcastStickyCache.invalidateCache(intent.getAction());
}
}
@@ -1724,6 +1750,7 @@ class BroadcastController {
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
+ final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
if (stickies != null) {
@@ -1740,12 +1767,16 @@ class BroadcastController {
if (list.size() <= 0) {
stickies.remove(intent.getAction());
}
+ changedStickyBroadcasts.add(intent.getAction());
}
if (stickies.size() <= 0) {
mStickyBroadcasts.remove(userId);
}
}
}
+ for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+ BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
+ }
}
void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -2124,9 +2155,18 @@ class BroadcastController {
}
void removeStickyBroadcasts(int userId) {
+ final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
+ final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(userId);
+ if (stickies != null) {
+ changedStickyBroadcasts.addAll(stickies.keySet());
+ }
mStickyBroadcasts.remove(userId);
}
+ for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+ BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
+ }
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index a32d3cb86c24..05aeb42dbc9f 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -57,7 +57,6 @@ public final class BroadcastFilter extends IntentFilter {
final boolean visibleToInstantApp;
public final boolean exported;
final int initialPriority;
- final ApplicationInfo applicationInfo;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
String _packageName, String _featureId, String _receiverId, String _requiredPermission,
@@ -74,10 +73,9 @@ public final class BroadcastFilter extends IntentFilter {
instantApp = _instantApp;
visibleToInstantApp = _visibleToInstantApp;
exported = _exported;
- applicationInfo = _applicationInfo;
initialPriority = getPriority();
setPriority(calculateAdjustedPriority(owningUid, initialPriority,
- applicationInfo, platformCompat));
+ _applicationInfo, platformCompat));
}
public @Nullable String getReceiverClassName() {
@@ -91,7 +89,7 @@ public final class BroadcastFilter extends IntentFilter {
}
public @NonNull ApplicationInfo getApplicationInfo() {
- return applicationInfo;
+ return receiverList.app.info;
}
@NeverCompile
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f86474f0dcaf..70febcd63455 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -22,6 +22,7 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
+import static android.content.pm.Flags.appCompatOption16kb;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -2025,6 +2026,16 @@ public final class ProcessList {
runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
}
+ if (appCompatOption16kb()) {
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+ if (is16KbDevice
+ && mService.mContext
+ .getPackageManager()
+ .isPageSizeCompatEnabled(app.info.packageName)) {
+ runtimeFlags |= Zygote.ENABLE_PAGE_SIZE_APP_COMPAT;
+ }
+ }
+
String invokeWith = null;
if (debuggableFlag) {
// Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7660c154efd5..883e09f53e41 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,7 +42,7 @@ import android.aconfigd.Aconfigd.StorageReturnMessages;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -140,9 +140,16 @@ public class SettingsToPropertiesMapper {
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "tv_os",
+ "aaos_carframework_triage",
+ "aaos_performance_triage",
+ "aaos_user_triage",
+ "aaos_window_triage",
"aaos_audio_triage",
"aaos_power_triage",
+ "aaos_storage_triage",
"aaos_sdv",
+ "aaos_vac_triage",
"accessibility",
"android_core_networking",
"android_health_services",
@@ -209,6 +216,7 @@ public class SettingsToPropertiesMapper {
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_continuity",
+ "pixel_display",
"pixel_perf",
"pixel_sensors",
"pixel_state_server",
@@ -458,17 +466,38 @@ public class SettingsToPropertiesMapper {
* @param requests: request proto output stream
* @return aconfigd socket return as proto input stream
*/
- static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
+ static void sendAconfigdRequests(ProtoOutputStream requests) {
+ ProtoInputStream returns = sendAconfigdRequests("aconfigd_system", requests);
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
+ if (enableAconfigdFromMainline()) {
+ returns = sendAconfigdRequests("aconfigd_mainline", requests);
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
+ }
+ }
+
+ /**
+ * apply flag local override in aconfig new storage
+ * @param socketName: the socket to send to
+ * @param requests: request proto output stream
+ * @return aconfigd socket return as proto input stream
+ */
+ static ProtoInputStream sendAconfigdRequests(String socketName, ProtoOutputStream requests) {
// connect to aconfigd socket
LocalSocket client = new LocalSocket();
- String socketName = enableSystemAconfigdRust()
- ? "aconfigd_system" : "aconfigd";
- try{
+ try {
client.connect(new LocalSocketAddress(
socketName, LocalSocketAddress.Namespace.RESERVED));
- Slog.d(TAG, "connected to aconfigd socket");
+ Slog.d(TAG, "connected to " + socketName + " socket");
} catch (IOException ioe) {
- logErr("failed to connect to aconfigd socket", ioe);
+ logErr("failed to connect to " + socketName + " socket", ioe);
return null;
}
@@ -487,9 +516,9 @@ public class SettingsToPropertiesMapper {
byte[] requests_bytes = requests.getBytes();
outputStream.writeInt(requests_bytes.length);
outputStream.write(requests_bytes, 0, requests_bytes.length);
- Slog.d(TAG, "flag override requests sent to aconfigd");
+ Slog.d(TAG, "flag override requests sent to " + socketName);
} catch (IOException ioe) {
- logErr("failed to send requests to aconfigd", ioe);
+ logErr("failed to send requests to " + socketName, ioe);
return null;
}
@@ -497,10 +526,10 @@ public class SettingsToPropertiesMapper {
try {
int num_bytes = inputStream.readInt();
ProtoInputStream returns = new ProtoInputStream(inputStream);
- Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+ Slog.d(TAG, "received " + num_bytes + " bytes back from " + socketName);
return returns;
} catch (IOException ioe) {
- logErr("failed to read requests return from aconfigd", ioe);
+ logErr("failed to read requests return from " + socketName, ioe);
return null;
}
}
@@ -641,15 +670,8 @@ public class SettingsToPropertiesMapper {
return;
}
- // send requests to aconfigd and obtain the return byte buffer
- ProtoInputStream returns = sendAconfigdRequests(requests);
-
- // deserialize back using proto input stream
- try {
- parseAndLogAconfigdReturn(returns);
- } catch (IOException ioe) {
- logErr("failed to parse aconfigd return", ioe);
- }
+ // send requests to aconfigd
+ sendAconfigdRequests(requests);
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -759,15 +781,8 @@ public class SettingsToPropertiesMapper {
return;
}
- // send requests to aconfigd and obtain the return
- ProtoInputStream returns = sendAconfigdRequests(requests);
-
- // deserialize back using proto input stream
- try {
- parseAndLogAconfigdReturn(returns);
- } catch (IOException ioe) {
- logErr("failed to parse aconfigd return", ioe);
- }
+ // send requests to aconfigd
+ sendAconfigdRequests(requests);
}
/**
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06c586f5e9c2..295e0443371d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3191,7 +3191,7 @@ public class AppOpsService extends IAppOpsService.Stub {
resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
Process.INVALID_UID, null, null,
Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
- "proxy " + message, shouldCollectMessage);
+ "proxy " + message, shouldCollectMessage, 1);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
@@ -3210,7 +3210,20 @@ public class AppOpsService extends IAppOpsService.Stub {
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, 1);
+ }
+
+ @Override
+ public void noteOperationsInBatch(Map batchedNoteOps) {
+ for (var entry : ((Map<AppOpsManager.NotedOp, Integer>) batchedNoteOps).entrySet()) {
+ AppOpsManager.NotedOp notedOp = entry.getKey();
+ int notedCount = entry.getValue();
+ mCheckOpsDelegateDispatcher.noteOperation(
+ notedOp.getOp(), notedOp.getUid(), notedOp.getPackageName(),
+ notedOp.getAttributionTag(), notedOp.getVirtualDeviceId(),
+ notedOp.getShouldCollectAsyncNotedOp(), notedOp.getMessage(),
+ notedOp.getShouldCollectMessage(), notedCount);
+ }
}
@Override
@@ -3228,7 +3241,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, 1);
}
@Override
@@ -3237,13 +3250,12 @@ public class AppOpsService extends IAppOpsService.Stub {
String message, boolean shouldCollectMessage) {
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, 1);
}
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
- @Nullable String attributionTag, int virtualDeviceId,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage, int notedCount) {
String resolvedPackageName;
if (!shouldUseNewCheckOp()) {
verifyIncomingUid(uid);
@@ -3278,14 +3290,14 @@ public class AppOpsService extends IAppOpsService.Stub {
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, notedCount);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, int proxyUid,
String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
@OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3388,11 +3400,11 @@ public class AppOpsService extends IAppOpsService.Stub {
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
+ getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
- shouldCollectMessage);
+ shouldCollectMessage, notedCount);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
@@ -3551,7 +3563,7 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode,
@Nullable String attributionTag, @OpFlags int flags, @NonNull String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
Objects.requireNonNull(message);
int callingUid = Binder.getCallingUid();
@@ -3559,42 +3571,51 @@ public class AppOpsService extends IAppOpsService.Stub {
final long token = Binder.clearCallingIdentity();
try {
synchronized (this) {
- Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
-
- RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
- AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
- attributionTag, message, System.currentTimeMillis());
- final boolean[] wasNoteForwarded = {false};
-
if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0
&& shouldCollectMessage) {
reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode,
attributionTag, message);
}
- if (callbacks != null) {
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null) {
+ return;
+ }
+
+ final boolean[] wasNoteForwarded = {false};
+ if (Flags.rateLimitBatchedNoteOpAsyncCallbacksEnabled()) {
+ notedCount = 1;
+ }
+
+ for (int i = 0; i < notedCount; i++) {
+ AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+ attributionTag, message, System.currentTimeMillis());
+ wasNoteForwarded[0] = false;
callbacks.broadcast((cb) -> {
try {
cb.opNoted(asyncNotedOp);
wasNoteForwarded[0] = true;
} catch (RemoteException e) {
Slog.e(TAG,
- "Could not forward noteOp of " + opCode + " to " + packageName
+ "Could not forward noteOp of " + opCode + " to "
+ + packageName
+ "/" + uid + "(" + attributionTag + ")", e);
}
});
- }
- if (!wasNoteForwarded[0]) {
- ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
- if (unforwardedOps == null) {
- unforwardedOps = new ArrayList<>(1);
- mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
- }
+ if (!wasNoteForwarded[0]) {
+ ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(
+ key);
+ if (unforwardedOps == null) {
+ unforwardedOps = new ArrayList<>(1);
+ mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+ }
- unforwardedOps.add(asyncNotedOp);
- if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
- unforwardedOps.remove(0);
+ unforwardedOps.add(asyncNotedOp);
+ if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+ unforwardedOps.remove(0);
+ }
}
}
}
@@ -4026,7 +4047,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (shouldCollectAsyncNotedOp && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, 1);
}
return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
@@ -7574,34 +7595,36 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- String message, boolean shouldCollectMessage) {
+ String message, boolean shouldCollectMessage, int notedCount) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, this::noteDelegateOperationImpl
+ shouldCollectMessage, notedCount, this::noteDelegateOperationImpl
);
} else {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, AppOpsService.this::noteOperationImpl
+ shouldCollectMessage, notedCount, AppOpsService.this::noteOperationImpl
);
}
} else if (mCheckOpsDelegate != null) {
return noteDelegateOperationImpl(code, uid, packageName, attributionTag,
- virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ notedCount);
}
return noteOperationImpl(code, uid, packageName, attributionTag,
- virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ notedCount);
}
private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
@Nullable String packageName, @Nullable String featureId, int virtualDeviceId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- AppOpsService.this::noteOperationImpl
+ notedCount, AppOpsService.this::noteOperationImpl
);
}
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 314664b0a79d..4d114b4ad4ac 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -100,10 +100,12 @@ final class AttributedOp {
* @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
+ * @param accessCount The number of times the op is accessed
*/
public void accessed(int proxyUid, @Nullable String proxyPackageName,
@Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ int accessCount) {
long accessTime = System.currentTimeMillis();
accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
uidState, flags);
@@ -111,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,
- DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
+ DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
}
/**
@@ -255,7 +257,7 @@ final class AttributedOp {
if (isStarted) {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
+ attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
}
}
@@ -451,7 +453,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), startTime, event.getAttributionFlags(),
- event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
+ event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 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/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 6b0253864e2b..5e67f26ba1f6 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -475,7 +475,7 @@ final class HistoricalRegistry {
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long accessTime,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteRegistry.AccessType int accessType) {
+ @DiscreteRegistry.AccessType int accessType, int accessCount) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -484,7 +484,7 @@ final class HistoricalRegistry {
}
getUpdatedPendingHistoricalOpsMLocked(
System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
- attributionTag, uidState, flags, 1);
+ attributionTag, uidState, flags, accessCount);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, accessTime, -1, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 473691874262..7502664a9628 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -27,6 +27,7 @@ import static android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT;
import static android.Manifest.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS;
+import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.RECORD_AUDIO;
@@ -84,6 +85,8 @@ public class AudioServerPermissionProvider {
MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT;
MONITORED_PERMS[PermissionEnum.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION] =
BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
+ MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS_PRIVILEGED] =
+ MODIFY_AUDIO_SETTINGS_PRIVILEGED;
}
private final Object mLock = new Object();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c6317bd29824..5928f8105cdf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -95,6 +95,7 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.PropertyInvalidatedCache;
import android.app.UidObserver;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -248,6 +249,7 @@ import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -4175,12 +4177,6 @@ public class AudioService extends IAudioService.Stub
// Stream mute changed, fire the intent.
Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
- if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) {
- intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- AudioSystem.STREAM_BLUETOOTH_SCO);
- // in this case broadcast for both sco and voice_call streams the mute status
- sendBroadcastToAll(intent, null /* options */);
- }
intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
sendBroadcastToAll(intent, null /* options */);
}
@@ -9554,9 +9550,11 @@ public class AudioService extends IAudioService.Stub
index = (mIndexMap.valueAt(i) + 5)/10;
}
- sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
- SENDMSG_REPLACE, device, isAbsoluteVolume ? 1 : 0, this,
- /*delay=*/0);
+ if (mStreamType == AudioSystem.STREAM_MUSIC) {
+ sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
+ SENDMSG_QUEUE, device, isAbsoluteVolume ? 1 : 0, this,
+ /*delay=*/0);
+ }
setStreamVolumeIndex(index, device);
}
@@ -9661,16 +9659,9 @@ public class AudioService extends IAudioService.Stub
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
oldIndex);
- int extraStreamType = mStreamType;
- // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
- if (isStreamBluetoothSco(mStreamType)) {
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- AudioSystem.STREAM_BLUETOOTH_SCO);
- extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO;
- } else {
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- mStreamType);
- }
+
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ mStreamType);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
streamAlias);
@@ -9681,21 +9672,9 @@ public class AudioService extends IAudioService.Stub
" aliased streams: " + aliasStreamIndexes;
}
AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- extraStreamType, aliasStreamIndexesString, index, oldIndex));
- if (extraStreamType != mStreamType) {
- AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, aliasStreamIndexesString, index, oldIndex));
- }
+ mStreamType, aliasStreamIndexesString, index, oldIndex));
}
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
- if (extraStreamType != mStreamType) {
- // send multiple intents in case we merged voice call and bt sco streams
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- mStreamType);
- // do not use the options in thid case which could discard
- // the previous intent
- sendBroadcastToAll(mVolumeChanged, null);
- }
}
}
}
@@ -10103,7 +10082,7 @@ public class AudioService extends IAudioService.Stub
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
- sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_REPLACE,
+ sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_QUEUE,
device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device) ? 1 : 0),
streamState, /*delay=*/0);
@@ -10939,14 +10918,122 @@ public class AudioService extends IAudioService.Stub
}
};
mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
- PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
task);
} else {
mAudioSystem.listenForSystemPropertyChange(
- PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
() -> mAudioServerLifecycleExecutor.execute(
mPermissionProvider::onPermissionStateChanged));
}
+
+ if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+ new PackageInfoTransducer().start();
+ }
+ }
+
+ /**
+ * A transducer that converts high-speed changes in the CACHE_KEY_PACKAGE_INFO_CACHE
+ * PropertyInvalidatedCache into low-speed changes in the CACHE_KEY_PACKAGE_INFO_NOTIFY system
+ * property. This operates on the popcorn principle: changes in the source are done when the
+ * source has been quiet for the soak interval.
+ *
+ * TODO(b/381097912) This is a temporary measure to support migration away from sysprop
+ * sniffing. It should be cleaned up.
+ */
+ private static class PackageInfoTransducer extends Thread {
+
+ // The run/stop signal.
+ private final AtomicBoolean mRunning = new AtomicBoolean(false);
+
+ // The source of change information.
+ private final PropertyInvalidatedCache.NonceWatcher mWatcher;
+
+ // The handler for scheduling delayed reactions to changes.
+ private final Handler mHandler;
+
+ // How long to soak changes: 50ms is the legacy choice.
+ private final static long SOAK_TIME_MS = 50;
+
+ // The ubiquitous lock.
+ private final Object mLock = new Object();
+
+ // If positive, this is the soak expiration time.
+ @GuardedBy("mLock")
+ private long mSoakDeadlineMs = -1;
+
+ // A source of unique long values.
+ @GuardedBy("mLock")
+ private long mToken = 0;
+
+ PackageInfoTransducer() {
+ mWatcher = PropertyInvalidatedCache
+ .getNonceWatcher(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
+ mHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ PackageInfoTransducer.this.handleMessage(msg);
+ }};
+ }
+
+ public void run() {
+ mRunning.set(true);
+ while (mRunning.get()) {
+ try {
+ final int changes = mWatcher.waitForChange();
+ if (changes == 0 || !mRunning.get()) {
+ continue;
+ }
+ } catch (InterruptedException e) {
+ // We don't know why the exception occurred but keep running until told to
+ // stop.
+ continue;
+ }
+ trigger();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateLocked() {
+ String n = Long.toString(mToken++);
+ SystemProperties.set(PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, n);
+ }
+
+ private void trigger() {
+ synchronized (mLock) {
+ boolean alreadyQueued = mSoakDeadlineMs >= 0;
+ final long nowMs = SystemClock.uptimeMillis();
+ mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
+ if (!alreadyQueued) {
+ mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
+ updateLocked();
+ }
+ }
+ }
+
+ private void handleMessage(Message msg) {
+ synchronized (mLock) {
+ if (mSoakDeadlineMs < 0) {
+ return; // ???
+ }
+ final long nowMs = SystemClock.uptimeMillis();
+ if (mSoakDeadlineMs > nowMs) {
+ mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
+ mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
+ return;
+ }
+ mSoakDeadlineMs = -1;
+ updateLocked();
+ }
+ }
+
+ /**
+ * Cause the thread to exit. Running is set to false and the watcher is awakened.
+ */
+ public void done() {
+ mRunning.set(false);
+ mWatcher.wakeUp();
+ }
}
//==========================================================================================
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 995e16bd8f17..1b5f0e5d31c8 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -657,8 +657,8 @@ public class AudioServiceEvents {
return "CSD lowering volume to RS1";
case UPDATE_ABS_VOLUME_ATTENUATION:
return String.format(java.util.Locale.US,
- "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
- mLongValue, mFloatValue);
+ "Updating CSD absolute volume attenuation on device 0x%s with %.2f dB ",
+ Long.toHexString(mLongValue), mFloatValue);
}
return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
}
diff --git a/services/core/java/com/android/server/audio/OWNERS b/services/core/java/com/android/server/audio/OWNERS
index b70de299eeea..709e4c235290 100644
--- a/services/core/java/com/android/server/audio/OWNERS
+++ b/services/core/java/com/android/server/audio/OWNERS
@@ -1,2 +1,3 @@
+atneya@google.com
jmtrivi@google.com
elaurent@google.com
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index afa90d5869e3..608edbb87861 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -129,6 +129,8 @@ public class SpatializerHelper {
/** current level as reported by native Spatializer in callback */
private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ /** cached version of Spatializer.getSpatializedChannelMasks */
+ private List<Integer> mSpatializedChannelMasks = Collections.emptyList();
private boolean mTransauralSupported = false;
private boolean mBinauralSupported = false;
@@ -1030,6 +1032,17 @@ public class SpatializerHelper {
return;
}
try {
+ final int[] nativeMasks = mSpat.getSpatializedChannelMasks();
+ for (int i = 0; i < nativeMasks.length; i++) {
+ nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
+ }
+ mSpatializedChannelMasks = Arrays.stream(nativeMasks).boxed().toList();
+
+ } catch (Exception e) { // just catch Exception in case nativeMasks is null
+ Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
+ mSpatializedChannelMasks = Collections.emptyList();
+ }
+ try {
//TODO: register heatracking callback only when sensors are registered
if (mIsHeadTrackingSupported) {
mActualHeadTrackingMode =
@@ -1103,20 +1116,7 @@ public class SpatializerHelper {
}
synchronized @NonNull List<Integer> getSpatializedChannelMasks() {
- if (!checkSpatializer("getSpatializedChannelMasks")) {
- return Collections.emptyList();
- }
- try {
- final int[] nativeMasks = new int[0]; // FIXME mSpat query goes here
- for (int i = 0; i < nativeMasks.length; i++) {
- nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
- }
- final List<Integer> masks = Arrays.stream(nativeMasks).boxed().toList();
- return masks;
- } catch (Exception e) { // just catch Exception in case nativeMasks is null
- Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
- return Collections.emptyList();
- }
+ return mSpatializedChannelMasks;
}
//------------------------------------------------------
@@ -1622,6 +1622,14 @@ public class SpatializerHelper {
pw.println("\tmState:" + mState);
pw.println("\tmSpatLevel:" + mSpatLevel);
pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel);
+ List<Integer> speakerMasks = getSpatializedChannelMasks();
+ StringBuilder masks = speakerMasks.isEmpty()
+ ? new StringBuilder("none") : new StringBuilder("");
+ for (Integer mask : speakerMasks) {
+ masks.append(AudioFormat.javaChannelOutMaskToString(mask)).append(" ");
+ }
+ pw.println("\tspatialized speaker masks: " + masks);
+
pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported);
StringBuilder modesString = new StringBuilder();
for (int mode : mSupportedHeadTrackingModes) {
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index d3da8dd2cfda..95ba69580bba 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -34,3 +34,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "frr_dialog_improvement"
+ namespace: "biometrics_framework"
+ description: "This flag controls FRR dialog improvement"
+ bug: "380800403"
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e1bb8a1a0f21..4c5f65285a9e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -26,13 +26,13 @@ import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
import static android.os.PowerWhitelistManager.REASON_VPN;
import static android.os.UserHandle.PER_USER_RANGE;
import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
import static java.util.Objects.requireNonNull;
@@ -103,6 +103,8 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.MtuUtils;
+import android.net.vcn.util.PersistableBundleUtils;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -150,8 +152,6 @@ import com.android.net.module.util.NetworkStackConstants;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.net.BaseNetworkObserver;
-import com.android.server.vcn.util.MtuUtils;
-import com.android.server.vcn.util.PersistableBundleUtils;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index fc20ef20f63d..7154bc47fc33 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -73,6 +73,8 @@ public class SyncLogger {
*/
public static synchronized SyncLogger getInstance() {
if (sInstance == null) {
+ // Always default to the sync logger for now (see b/381957278#comment8).
+ /*
final String flag = SystemProperties.get("debug.synclog");
final boolean enable =
(Build.IS_DEBUGGABLE
@@ -83,6 +85,8 @@ public class SyncLogger {
} else {
sInstance = new SyncLogger();
}
+ */
+ sInstance = new SyncLogger();
}
return sInstance;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8b9c664a31fd..251344395ae3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,7 +19,16 @@ package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_DUAL_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_REAR_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
@@ -44,6 +53,10 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IProcessObserver;
import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
@@ -56,9 +69,12 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.LongSparseLongArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -82,6 +98,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
@@ -130,6 +147,8 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull
private final BinderService mBinderService;
@NonNull
+ private final HalService mHalService;
+ @NonNull
private final OverrideRequestController mOverrideRequestController;
@NonNull
private final DeviceStateProviderListener mDeviceStateProviderListener;
@@ -139,7 +158,7 @@ public final class DeviceStateManagerService extends SystemService {
// All supported device states keyed by identifier.
@GuardedBy("mLock")
- private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
+ private final SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
// The current committed device state. Will be empty until the first device state provided by
// the DeviceStateProvider is committed.
@@ -177,7 +196,7 @@ public final class DeviceStateManagerService extends SystemService {
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
- private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
+ private final Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
private Set<Integer> mFoldedDeviceStates = new HashSet<>();
@@ -259,6 +278,7 @@ public final class DeviceStateManagerService extends SystemService {
mDeviceStateProviderListener = new DeviceStateProviderListener();
mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
+ mHalService = new HalService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mDeviceStateNotificationController = new DeviceStateNotificationController(
context, mHandler,
@@ -272,6 +292,10 @@ public final class DeviceStateManagerService extends SystemService {
@Override
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+ String halServiceName = IDeviceStateService.DESCRIPTOR + "/default";
+ if (ServiceManager.isDeclared(halServiceName)) {
+ publishBinderService(halServiceName, mHalService);
+ }
publishLocalService(DeviceStateManagerInternal.class, new LocalService());
if (!Flags.deviceStatePropertyMigration()) {
@@ -440,6 +464,11 @@ public final class DeviceStateManagerService extends SystemService {
return mBinderService;
}
+ @VisibleForTesting
+ IDeviceStateService getHalBinderService() {
+ return mHalService;
+ }
+
private void updateSupportedStates(DeviceState[] supportedDeviceStates,
@DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
@@ -1282,6 +1311,124 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ private final class HalService extends IDeviceStateService.Stub {
+ private final LongSparseLongArray mPublicProperties = new LongSparseLongArray();
+ public HalService() {
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+ mPublicProperties.put(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN,
+ FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+ mPublicProperties.put(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ mPublicProperties.put(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+ mPublicProperties.put(
+ PROPERTY_FEATURE_REAR_DISPLAY,
+ FEATURE_REAR_DISPLAY);
+ mPublicProperties.put(
+ PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+ FEATURE_DUAL_DISPLAY);
+ }
+
+ private final class HalBinderCallback implements IDeviceStateManagerCallback {
+ private final IDeviceStateListener mListener;
+
+ private HalBinderCallback(@NonNull IDeviceStateListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onDeviceStateInfoChanged(DeviceStateInfo info) throws RemoteException {
+ DeviceStateConfiguration config = new DeviceStateConfiguration();
+ Set<Integer> systemProperties = new HashSet<>(
+ info.currentState.getConfiguration().getSystemProperties());
+ Set<Integer> physicalProperties = new HashSet<>(
+ info.currentState.getConfiguration().getPhysicalProperties());
+ config.deviceProperties = 0;
+ for (Integer prop : systemProperties) {
+ Long publicProperty = mPublicProperties.get(prop);
+ if (publicProperty != null) {
+ config.deviceProperties |= publicProperty.longValue();
+ }
+ }
+ for (Integer prop : physicalProperties) {
+ Long publicProperty = mPublicProperties.get(prop);
+ if (publicProperty != null) {
+ config.deviceProperties |= publicProperty.longValue();
+ }
+ }
+ mListener.onDeviceStateChanged(config);
+ }
+
+ @Override
+ public void onRequestActive(IBinder token) {
+ //No-op
+ }
+
+ @Override
+ public void onRequestCanceled(IBinder token) {
+ //No-op
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mListener.asBinder();
+ }
+ }
+
+ @Override
+ public void registerListener(IDeviceStateListener listener) throws RemoteException {
+ if (listener == null) {
+ throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+ }
+
+ final int callingPid = Binder.getCallingPid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ HalBinderCallback callback = new HalBinderCallback(listener);
+ DeviceStateInfo info = registerProcess(callingPid, callback);
+ if (info != null) {
+ callback.onDeviceStateInfoChanged(info);
+ }
+ } catch (SecurityException e) {
+ throw new ServiceSpecificException(ErrorCode.ALREADY_EXISTS);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterListener(IDeviceStateListener listener) throws RemoteException {
+ final int callingPid = Binder.getCallingPid();
+
+ synchronized (mLock) {
+ if (mProcessRecords.contains(callingPid)) {
+ mProcessRecords.remove(callingPid);
+ return;
+ }
+ }
+
+ throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return IDeviceStateService.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return IDeviceStateService.HASH;
+ }
+ }
+
/** Implementation of {@link IDeviceStateManager} published as a binder service. */
private final class BinderService extends IDeviceStateManager.Stub {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 0f65360e9ee4..c19d2c9091c3 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -130,6 +130,8 @@ public class BrightnessTracker {
private static final int MSG_START_SENSOR_LISTENER = 3;
private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4;
private static final int MSG_SENSOR_CHANGED = 5;
+ private static final int MSG_START_DISPLAY_LISTENER = 6;
+ private static final int MSG_STOP_DISPLAY_LISTENER = 7;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -159,7 +161,9 @@ public class BrightnessTracker {
private SensorListener mSensorListener;
private Sensor mLightSensor;
private SettingsObserver mSettingsObserver;
- private DisplayListener mDisplayListener;
+ private final DisplayListener mDisplayListener = new DisplayListener();
+ private boolean mDisplayListenerRegistered;
+ private boolean mIsDisplayActive;
private boolean mSensorRegistered;
private boolean mColorSamplingEnabled;
private int mNoFramesToSample;
@@ -231,6 +235,8 @@ public class BrightnessTracker {
mSettingsObserver = new SettingsObserver(mBgHandler);
mInjector.registerBrightnessModeObserver(mContentResolver, mSettingsObserver);
+ startDisplayListener();
+ mIsDisplayActive = isDisplayActive();
startSensorListener();
final IntentFilter intentFilter = new IntentFilter();
@@ -260,6 +266,7 @@ public class BrightnessTracker {
Slog.d(TAG, "Stop");
}
mBgHandler.removeMessages(MSG_BACKGROUND_START);
+ stopDisplayListener();
stopSensorListener();
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterBrightnessModeObserver(mContext, mSettingsObserver);
@@ -443,6 +450,11 @@ public class BrightnessTracker {
private void handleSensorChanged(Sensor lightSensor) {
if (mLightSensor != lightSensor) {
mLightSensor = lightSensor;
+ if (lightSensor != null) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
+ } else {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
+ }
stopSensorListener();
// Attempt to restart the sensor listener. It will check to see if it should be running
// so there is no need to also check here.
@@ -455,6 +467,7 @@ public class BrightnessTracker {
&& mLightSensor != null
&& mAmbientBrightnessStatsTracker != null
&& mInjector.isInteractive(mContext)
+ && mIsDisplayActive
&& mInjector.isBrightnessModeAutomatic(mContentResolver)) {
mAmbientBrightnessStatsTracker.start();
mSensorRegistered = true;
@@ -825,11 +838,14 @@ public class BrightnessTracker {
pw.println(" mColorSamplingEnabled=" + mColorSamplingEnabled);
pw.println(" mNoFramesToSample=" + mNoFramesToSample);
pw.println(" mFrameRate=" + mFrameRate);
+ pw.println(" mIsDisplayActive=" + mIsDisplayActive);
+ pw.println(" mDisplayListenerRegistered=" + mDisplayListenerRegistered);
}
private void enableColorSampling() {
if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
|| !mInjector.isInteractive(mContext)
+ || !mIsDisplayActive
|| mColorSamplingEnabled
|| !mShouldCollectColorSample) {
return;
@@ -860,10 +876,6 @@ public class BrightnessTracker {
+ mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
}
}
- if (mColorSamplingEnabled && mDisplayListener == null) {
- mDisplayListener = new DisplayListener();
- mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
- }
}
private void disableColorSampling() {
@@ -872,10 +884,6 @@ public class BrightnessTracker {
}
mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
mColorSamplingEnabled = false;
- if (mDisplayListener != null) {
- mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
- mDisplayListener = null;
- }
if (DEBUG) {
Slog.i(TAG, "turning off color sampling");
}
@@ -913,6 +921,25 @@ public class BrightnessTracker {
}
}
+ private boolean isDisplayActive() {
+ return Display.isActiveState(mInjector.getDisplayState(mContext));
+ }
+
+ private void startDisplayListener() {
+ if (!mDisplayListenerRegistered && mLightSensor != null && mInjector.isInteractive(mContext)
+ && mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+ mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
+ mDisplayListenerRegistered = true;
+ }
+ }
+
+ private void stopDisplayListener() {
+ if (mDisplayListenerRegistered) {
+ mInjector.unregisterDisplayListener(mContext, mDisplayListener);
+ mDisplayListenerRegistered = false;
+ }
+ }
+
private final class SensorListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
@@ -941,6 +968,15 @@ public class BrightnessTracker {
public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
updateColorSampling();
+ boolean isDisplayActive = isDisplayActive();
+ if (mIsDisplayActive != isDisplayActive) {
+ mIsDisplayActive = isDisplayActive;
+ if (isDisplayActive) {
+ mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
+ } else {
+ mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
+ }
+ }
}
}
}
@@ -956,8 +992,10 @@ public class BrightnessTracker {
Slog.v(TAG, "settings change " + uri);
}
if (mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
} else {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
}
}
@@ -980,8 +1018,10 @@ public class BrightnessTracker {
batteryLevelChanged(level, scale);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
}
}
@@ -1023,7 +1063,12 @@ public class BrightnessTracker {
case MSG_SENSOR_CHANGED:
handleSensorChanged((Sensor) msg.obj);
break;
-
+ case MSG_START_DISPLAY_LISTENER:
+ startDisplayListener();
+ break;
+ case MSG_STOP_DISPLAY_LISTENER:
+ stopDisplayListener();
+ break;
}
}
}
@@ -1151,6 +1196,12 @@ public class BrightnessTracker {
return context.getSystemService(PowerManager.class).isInteractive();
}
+ public int getDisplayState(Context context) {
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ return display.getState();
+ }
+
public int getNightDisplayColorTemperature(Context context) {
return context.getSystemService(ColorDisplayManager.class)
.getNightDisplayColorTemperature();
@@ -1208,7 +1259,7 @@ public class BrightnessTracker {
displayManager.registerDisplayListener(listener, handler);
}
- public void unRegisterDisplayListener(Context context,
+ public void unregisterDisplayListener(Context context,
DisplayManager.DisplayListener listener) {
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
displayManager.unregisterDisplayListener(listener);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c4e10360a7cb..e10bdaab4b97 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -765,6 +765,7 @@ public class DisplayDeviceConfig {
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
+ private float mBrightnessDim = Float.NaN;
private float mBrightnessRampFastDecrease = Float.NaN;
private float mBrightnessRampFastIncrease = Float.NaN;
private float mBrightnessRampSlowDecrease = Float.NaN;
@@ -1282,6 +1283,24 @@ public class DisplayDeviceConfig {
}
/**
+ * Return the minimum brightness on a scale of 0.0f - 1.0f
+ *
+ * @return minimum brightness
+ */
+ public float getBrightnessMinimum() {
+ return getBrightnessFromBacklight(mBacklightMinimum);
+ }
+
+ /**
+ * Return the maximum brightness on a scale of 0.0f - 1.0f
+ *
+ * @return maximum brightness
+ */
+ public float getBrightnessMaximum() {
+ return getBrightnessFromBacklight(mBacklightMaximum);
+ }
+
+ /**
* Return the default brightness on a scale of 0.0f - 1.0f
*
* @return default brightness
@@ -1290,6 +1309,15 @@ public class DisplayDeviceConfig {
return mBrightnessDefault;
}
+ /**
+ * Return the dim brightness on a scale of 0.0f - 1.0f
+ *
+ * @return dim brightness
+ */
+ public float getBrightnessDim() {
+ return mBrightnessDim;
+ }
+
public float getBrightnessRampFastDecrease() {
return mBrightnessRampFastDecrease;
}
@@ -1689,6 +1717,7 @@ public class DisplayDeviceConfig {
+ ", mBacklightMinimum=" + mBacklightMinimum
+ ", mBacklightMaximum=" + mBacklightMaximum
+ ", mBrightnessDefault=" + mBrightnessDefault
+ + ", mBrightnessDim=" + mBrightnessDim
+ ", mQuirks=" + mQuirks
+ "\n"
+ "mLuxThrottlingData=" + mLuxThrottlingData
@@ -1906,6 +1935,7 @@ public class DisplayDeviceConfig {
mBacklightMinimum = PowerManager.BRIGHTNESS_MIN;
mBacklightMaximum = PowerManager.BRIGHTNESS_MAX;
mBrightnessDefault = BRIGHTNESS_DEFAULT;
+ mBrightnessDim = PowerManager.BRIGHTNESS_INVALID;
mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX;
mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX;
mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX;
@@ -2003,6 +2033,15 @@ public class DisplayDeviceConfig {
mBacklightMinimum = min;
mBacklightMaximum = max;
}
+ final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessDimFloat);
+ if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(com.android.internal.R.integer
+ .config_screenBrightnessDim));
+ } else {
+ mBrightnessDim = dim;
+ }
}
private void loadBrightnessMap(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 1c1bdad01034..3aaf4f6fe85a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -477,6 +477,7 @@ final class DisplayDeviceInfo {
public float brightnessMinimum;
public float brightnessMaximum;
public float brightnessDefault;
+ public float brightnessDim;
// NaN means unsupported
public float hdrSdrRatio = Float.NaN;
@@ -561,8 +562,8 @@ final class DisplayDeviceInfo {
|| !Objects.equals(ownerPackageName, other.ownerPackageName)
|| !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
|| !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
- || !BrightnessSynchronizer.floatEquals(brightnessDefault,
- other.brightnessDefault)
+ || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault)
+ || !BrightnessSynchronizer.floatEquals(brightnessDim, other.brightnessDim)
|| !Objects.equals(roundedCorners, other.roundedCorners)
|| installOrientation != other.installOrientation
|| !Objects.equals(displayShape, other.displayShape)
@@ -618,6 +619,7 @@ final class DisplayDeviceInfo {
brightnessMinimum = other.brightnessMinimum;
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
+ brightnessDim = other.brightnessDim;
hdrSdrRatio = other.hdrSdrRatio;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
@@ -672,6 +674,7 @@ final class DisplayDeviceInfo {
sb.append(", brightnessMinimum ").append(brightnessMinimum);
sb.append(", brightnessMaximum ").append(brightnessMaximum);
sb.append(", brightnessDefault ").append(brightnessDefault);
+ sb.append(", brightnessDim ").append(brightnessDim);
sb.append(", hdrSdrRatio ").append(hdrSdrRatio);
if (roundedCorners != null) {
sb.append(", roundedCorners ").append(roundedCorners);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3871f2a57f76..452dc5f97d12 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -47,6 +47,7 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
@@ -71,6 +72,7 @@ import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -534,6 +536,8 @@ public final class DisplayManagerService extends SystemService {
private final boolean mExtraDisplayEventLogging;
private final String mExtraDisplayLoggingPackageName;
+ private boolean mMirrorBuiltInDisplay;
+
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -668,7 +672,8 @@ public final class DisplayManagerService extends SystemService {
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
if (mFlags.isDisplayTopologyEnabled()) {
mDisplayTopologyCoordinator =
- new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled);
+ new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled,
+ this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot);
} else {
mDisplayTopologyCoordinator = null;
}
@@ -781,7 +786,11 @@ public final class DisplayManagerService extends SystemService {
}
dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
- handleSettingsChange();
+ handleMinimalPostProcessingAllowedSettingChange();
+
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
}
}
@@ -824,12 +833,15 @@ public final class DisplayManagerService extends SystemService {
// relevant configuration should be in place.
recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
- updateSettingsLocked();
+ updateMinimalPostProcessingAllowedSettingLocked();
updateUserDisabledHdrTypesFromSettingsLocked();
updateUserPreferredDisplayModeSettingsLocked();
if (mIsHdrOutputControlEnabled) {
updateHdrConversionModeSettingsLocked();
}
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
}
mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1179,27 +1191,61 @@ public final class DisplayManagerService extends SystemService {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED), false, this);
+
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ MIRROR_BUILT_IN_DISPLAY), false, this, UserHandle.USER_ALL);
+ }
}
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange();
+ if (Settings.Secure.getUriFor(
+ Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED).equals(uri)) {
+ handleMinimalPostProcessingAllowedSettingChange();
+ return;
+ }
+
+ if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) {
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
+ return;
+ }
}
}
- private void handleSettingsChange() {
+ private void handleMinimalPostProcessingAllowedSettingChange() {
synchronized (mSyncRoot) {
- updateSettingsLocked();
+ updateMinimalPostProcessingAllowedSettingLocked();
scheduleTraversalLocked(false);
}
}
- private void updateSettingsLocked() {
+ private void updateMinimalPostProcessingAllowedSettingLocked() {
setMinimalPostProcessingAllowed(Settings.Secure.getIntForUser(
mContext.getContentResolver(), Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED,
1, UserHandle.USER_CURRENT) != 0);
}
+ private void updateMirrorBuiltInDisplaySettingLocked() {
+ if (!mFlags.isDisplayContentModeManagementEnabled()) {
+ Slog.e(TAG, "MirrorBuiltInDisplay setting shouldn't be updated when the flag is off.");
+ return;
+ }
+
+ synchronized (mSyncRoot) {
+ ContentResolver resolver = mContext.getContentResolver();
+ final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
+ MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0;
+ if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) {
+ return;
+ }
+ mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
+ }
+ }
+
private void restoreResolutionFromBackup() {
int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.SCREEN_RESOLUTION_MODE,
@@ -1681,7 +1727,12 @@ public final class DisplayManagerService extends SystemService {
if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
return checkCallingPermission(ADD_MIRROR_DISPLAY, "canCreateMirrorDisplays");
}
- return virtualDevice != null;
+ try {
+ return virtualDevice != null && virtualDevice.canCreateMirrorDisplays();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query virtual device for permissions", e);
+ return false;
+ }
}
private boolean canProjectVideo(IMediaProjection projection) {
@@ -3502,6 +3553,28 @@ public final class DisplayManagerService extends SystemService {
callbackRecord.notifyDisplayEventAsync(displayId, event);
}
+ private void deliverTopologyUpdate(DisplayTopology topology) {
+ if (DEBUG) {
+ Slog.d(TAG, "Delivering topology update");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
+ }
+
+ // Grab the lock and copy the callbacks.
+ List<CallbackRecord> callbacks = new ArrayList<>();
+ synchronized (mSyncRoot) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ callbacks.add(mCallbacks.valueAt(i));
+ }
+ }
+
+ // After releasing the lock, send the notifications out.
+ for (CallbackRecord callback : callbacks) {
+ callback.notifyTopologyUpdateAsync(topology);
+ }
+ }
+
private boolean extraLogging(String packageName) {
return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
@@ -4137,7 +4210,7 @@ public final class DisplayManagerService extends SystemService {
* cached or frozen.
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
- if (!shouldSendEvent(event)) {
+ if (!shouldSendDisplayEvent(event)) {
if (extraLogging(mPackageName)) {
Slog.i(TAG,
"Not sending displayEvent: " + event + " due to mask:"
@@ -4191,7 +4264,7 @@ public final class DisplayManagerService extends SystemService {
/**
* Return true if the client is interested in this event.
*/
- private boolean shouldSendEvent(@DisplayEvent int event) {
+ private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
final long mask = mInternalEventFlagsMask.get();
switch (event) {
case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
@@ -4252,6 +4325,45 @@ public final class DisplayManagerService extends SystemService {
mPendingEvents.add(new Event(displayId, event));
}
+ /**
+ * @return {@code false} if RemoteException happens; otherwise {@code true} for
+ * success.
+ */
+ boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
+ if ((mInternalEventFlagsMask.get()
+ & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED) == 0) {
+ if (extraLogging(mPackageName)) {
+ Slog.i(TAG, "Not sending topology update: " + topology + " due to mask: "
+ + mInternalEventFlagsMask);
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyTopologyUpdateAsync#notSendingUpdate=" + topology
+ + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
+ }
+ // The client is not interested in this event, so do nothing.
+ return true;
+ }
+ return transmitTopologyUpdate(topology);
+ }
+
+ /**
+ * Transmit a single display topology update. The client is presumed ready. Return true on
+ * success and false if the client died.
+ */
+ private boolean transmitTopologyUpdate(DisplayTopology topology) {
+ // The client is ready to receive the event.
+ try {
+ mCallback.onTopologyChanged(topology);
+ return true;
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that display topology changed, assuming it died.", ex);
+ binderDied();
+ return false;
+ }
+ }
+
// Send all pending events. This can safely be called if the process is not ready, but it
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 47226861545f..5b78726cc421 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -25,7 +25,9 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
/**
* Manages the relative placement (topology) of extended displays. Responsible for updating and
@@ -33,7 +35,7 @@ import java.util.function.BooleanSupplier;
*/
class DisplayTopologyCoordinator {
- @GuardedBy("mLock")
+ @GuardedBy("mSyncRoot")
private DisplayTopology mTopology;
/**
@@ -41,16 +43,31 @@ class DisplayTopologyCoordinator {
*/
private final BooleanSupplier mIsExtendedDisplayEnabled;
- private final Object mLock = new Object();
-
- DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled) {
- this(new Injector(), isExtendedDisplayEnabled);
+ /**
+ * Callback used to send topology updates.
+ * Should be invoked from the corresponding executor.
+ * A copy of the topology should be sent that will not be modified by the system.
+ */
+ private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
+ private final Executor mTopologyChangeExecutor;
+ private final DisplayManagerService.SyncRoot mSyncRoot;
+
+ DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
+ this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
+ topologyChangeExecutor, syncRoot);
}
@VisibleForTesting
- DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled) {
+ DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
mTopology = injector.getTopology();
mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
+ mOnTopologyChangedCallback = onTopologyChangedCallback;
+ mTopologyChangeExecutor = topologyChangeExecutor;
+ mSyncRoot = syncRoot;
}
/**
@@ -61,8 +78,9 @@ class DisplayTopologyCoordinator {
if (!isDisplayAllowedInTopology(info)) {
return;
}
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
+ sendTopologyUpdateLocked();
}
}
@@ -71,8 +89,9 @@ class DisplayTopologyCoordinator {
* @param displayId The logical display ID
*/
void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.removeDisplay(displayId);
+ sendTopologyUpdateLocked();
}
}
@@ -80,14 +99,16 @@ class DisplayTopologyCoordinator {
* @return A deep copy of the topology.
*/
DisplayTopology getTopology() {
- synchronized (mLock) {
- return mTopology;
+ synchronized (mSyncRoot) {
+ return mTopology.copy();
}
}
void setTopology(DisplayTopology topology) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology = topology;
+ mTopology.normalize();
+ sendTopologyUpdateLocked();
}
}
@@ -96,7 +117,7 @@ class DisplayTopologyCoordinator {
* @param pw The stream to dump information to.
*/
void dump(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.dump(pw);
}
}
@@ -124,6 +145,12 @@ class DisplayTopologyCoordinator {
&& info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
}
+ @GuardedBy("mSyncRoot")
+ private void sendTopologyUpdateLocked() {
+ DisplayTopology copy = mTopology.copy();
+ mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(copy));
+ }
+
@VisibleForTesting
static class Injector {
DisplayTopology getTopology() {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0b8f7d5ef2cf..d37dd3018fde 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -812,9 +812,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
- mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
- mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+ mInfo.brightnessMinimum = getDisplayDeviceConfig().getBrightnessMinimum();
+ mInfo.brightnessMaximum = getDisplayDeviceConfig().getBrightnessMaximum();
mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
+ mInfo.brightnessDim = getDisplayDeviceConfig().getBrightnessDim();
mInfo.hdrSdrRatio = mCurrentHdrSdrRatio;
}
return mInfo;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 85465981c473..1de9c9589fb9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -548,6 +548,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+ mBaseDisplayInfo.brightnessDim = deviceInfo.brightnessDim;
mBaseDisplayInfo.hdrSdrRatio = deviceInfo.hdrSdrRatio;
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index eb76dcba3b85..382c88327523 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -73,9 +73,13 @@ import java.util.regex.Pattern;
* </pre>
* Supported flags:
* <ul>
- * <li><pre>secure</pre>: creates a secure display</li>
- * <li><pre>own_content_only</pre>: only shows this display's own content</li>
- * <li><pre>should_show_system_decorations</pre>: supports system decorations</li>
+ * <li><code>secure</code>: creates a secure display</li>
+ * <li><code>own_content_only</code>: only shows this display's own content</li>
+ * <li><code>should_show_system_decorations</code>: supports system decorations</li>
+ * <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li>
+ * <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li>
+ * <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li>
+ * <li><code>gravity_bottom_left</code>: display the overlay at the bottom left of the screen</li>
* </ul>
* </p><p>
* Example:
@@ -113,6 +117,12 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
"should_show_system_decorations";
+ // Gravity flags to decide where the overlay should be shown.
+ private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
+ private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
+ private static final String GRAVITY_TOP_RIGHT = "gravity_top_right";
+ private static final String GRAVITY_BOTTOM_LEFT = "gravity_bottom_left";
+
private static final int MIN_WIDTH = 100;
private static final int MIN_HEIGHT = 100;
private static final int MAX_WIDTH = 4096;
@@ -237,8 +247,11 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
String name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_overlay_display_name,
number);
- int gravity = chooseOverlayGravity(number);
OverlayFlags flags = OverlayFlags.parseFlags(flagString);
+ int gravity = flags.mGravity;
+ if (flags.mGravity == Gravity.NO_GRAVITY) {
+ gravity = chooseOverlayGravity(number);
+ }
Slog.i(TAG, "Showing overlay display device #" + number
+ ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())
@@ -266,6 +279,16 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
}
}
+ private static int parseOverlayGravity(String overlayGravity) {
+ return switch (overlayGravity) {
+ case GRAVITY_TOP_LEFT -> Gravity.TOP | Gravity.LEFT;
+ case GRAVITY_TOP_RIGHT -> Gravity.TOP | Gravity.RIGHT;
+ case GRAVITY_BOTTOM_RIGHT -> Gravity.BOTTOM | Gravity.RIGHT;
+ case GRAVITY_BOTTOM_LEFT -> Gravity.BOTTOM | Gravity.LEFT;
+ default -> Gravity.NO_GRAVITY;
+ };
+ }
+
private abstract class OverlayDisplayDevice extends DisplayDevice {
private final String mName;
private final float mRefreshRate;
@@ -605,13 +628,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
/** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */
final boolean mShouldShowSystemDecorations;
+ final int mGravity;
+
OverlayFlags(
boolean secure,
boolean ownContentOnly,
- boolean shouldShowSystemDecorations) {
+ boolean shouldShowSystemDecorations,
+ int gravity) {
mSecure = secure;
mOwnContentOnly = ownContentOnly;
mShouldShowSystemDecorations = shouldShowSystemDecorations;
+ mGravity = gravity;
}
static OverlayFlags parseFlags(@Nullable String flagString) {
@@ -619,24 +646,26 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
return new OverlayFlags(
false /* secure */,
false /* ownContentOnly */,
- false /* shouldShowSystemDecorations */);
+ false /* shouldShowSystemDecorations */,
+ Gravity.NO_GRAVITY);
}
boolean secure = false;
boolean ownContentOnly = false;
boolean shouldShowSystemDecorations = false;
+ int gravity = Gravity.NO_GRAVITY;
for (String flag: flagString.split(FLAG_SPLITTER)) {
if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
secure = true;
- }
- if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
+ } else if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
ownContentOnly = true;
- }
- if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
+ } else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
shouldShowSystemDecorations = true;
+ } else {
+ gravity = parseOverlayGravity(flag);
}
}
- return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations);
+ return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity);
}
@Override
@@ -645,6 +674,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
.append("secure=").append(mSecure)
.append(", ownContentOnly=").append(mOwnContentOnly)
.append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
+ .append(", gravity").append(Gravity.toString(mGravity))
.append("}")
.toString();
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index dabef84fec31..836f4ede8f57 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.IBrightnessListener;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -183,8 +184,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
if (projection != null) {
mediaProjectionCallback = new MediaProjectionCallback(appToken);
}
+
+ Callback callbackDelegate = new Callback(
+ callback, virtualDisplayConfig.getBrightnessListener(), mHandler);
VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
- ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),
+ ownerUid, ownerPackageName, surface, flags, callbackDelegate,
projection, mediaProjectionCallback, uniqueId, virtualDisplayConfig);
mVirtualDisplayDevices.put(appToken, device);
@@ -336,7 +340,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private boolean mIsWindowManagerMirroring;
private final DisplayCutout mDisplayCutout;
private final float mDefaultBrightness;
+ private final float mDimBrightness;
private float mCurrentBrightness;
+ private final IBrightnessListener mBrightnessListener;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -354,7 +360,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
- mCurrentBrightness = mDefaultBrightness;
+ mDimBrightness = virtualDisplayConfig.getDimBrightness();
+ mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
+ mBrightnessListener = virtualDisplayConfig.getBrightnessListener();
mMode = createMode(mWidth, mHeight, getRefreshRate());
mSurface = surface;
mFlags = flags;
@@ -464,6 +472,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
}
if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mBrightnessListener != null
&& BrightnessUtils.isValidBrightnessValue(brightnessState)
&& brightnessState != mCurrentBrightness) {
mCurrentBrightness = brightnessState;
@@ -638,6 +647,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
mInfo.brightnessDefault = mDefaultBrightness;
+ mInfo.brightnessDim = mDimBrightness;
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
@@ -661,10 +671,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3;
private final IVirtualDisplayCallback mCallback;
+ private final IBrightnessListener mBrightnessListener;
- public Callback(IVirtualDisplayCallback callback, Handler handler) {
+ Callback(IVirtualDisplayCallback callback, IBrightnessListener brightnessListener,
+ Handler handler) {
super(handler.getLooper());
mCallback = callback;
+ mBrightnessListener = brightnessListener;
}
@Override
@@ -681,7 +694,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCallback.onStopped();
break;
case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED:
- mCallback.onRequestedBrightnessChanged((Float) msg.obj);
+ if (mBrightnessListener != null) {
+ mBrightnessListener.onBrightnessChanged((Float) msg.obj);
+ }
break;
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
deleted file mode 100644
index a1fd16476706..000000000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ /dev/null
@@ -1,78 +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.server.display.brightness.clamper;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.PowerManager;
-
-import com.android.server.display.DisplayBrightnessState;
-
-import java.io.PrintWriter;
-
-/**
- * Provides brightness range constraints
- */
-abstract class BrightnessClamper<T> {
-
- protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
- protected boolean mIsActive = false;
-
- @NonNull
- protected final Handler mHandler;
-
- @NonNull
- protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
-
- BrightnessClamper(Handler handler,
- BrightnessClamperController.ClamperChangeListener changeListener) {
- mHandler = handler;
- mChangeListener = changeListener;
- }
-
- float getBrightnessCap() {
- return mBrightnessCap;
- }
-
- float getCustomAnimationRate() {
- return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- }
-
- boolean isActive() {
- return mIsActive;
- }
-
- void dump(PrintWriter writer) {
- writer.println("BrightnessClamper:" + getType());
- writer.println(" mBrightnessCap: " + mBrightnessCap);
- writer.println(" mIsActive: " + mIsActive);
- }
-
- @NonNull
- abstract Type getType();
-
- abstract void onDeviceConfigChanged();
-
- abstract void onDisplayChanged(T displayData);
-
- abstract void stop();
-
- protected enum Type {
- POWER,
- }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 6e579bf161ee..860be2028eb3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -18,8 +18,6 @@ package com.android.server.display.brightness.clamper;
import static android.view.Display.STATE_ON;
-import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -33,8 +31,8 @@ import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import android.util.Spline;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
@@ -43,7 +41,6 @@ import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -62,10 +59,10 @@ public class BrightnessClamperController {
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final LightSensorController mLightSensorController;
+ private int mDisplayState = Display.STATE_OFF;
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
- private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
private final List<BrightnessStateModifier> mModifiers;
@@ -77,16 +74,6 @@ public class BrightnessClamperController {
private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
- private final DisplayManagerFlags mDisplayManagerFlags;
- private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
- private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- @Nullable
- private BrightnessPowerClamper mPowerClamper;
- @Nullable
- private Type mClamperType = null;
-
- private boolean mClamperApplied = false;
private final LightSensorController.LightSensorListener mLightSensorListener =
new LightSensorController.LightSensorListener() {
@@ -96,6 +83,8 @@ public class BrightnessClamperController {
}
};
+ private volatile boolean mStarted = false;
+
public BrightnessClamperController(Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
@@ -109,32 +98,21 @@ public class BrightnessClamperController {
DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
- mDisplayManagerFlags = flags;
mLightSensorController = injector.getLightSensorController(sensorManager, context,
mLightSensorListener, mHandler);
mClamperChangeListenerExternal = clamperChangeListener;
mExecutor = new HandlerExecutor(handler);
- Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+ Runnable modifiersChangeRunnableInternal = this::recalculateModifiersState;
ClamperChangeListener clamperChangeListenerInternal = () -> {
- if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
- mHandler.post(clamperChangeRunnableInternal);
+ if (mStarted && !mHandler.hasCallbacks(modifiersChangeRunnableInternal)) {
+ mHandler.post(modifiersChangeRunnableInternal);
}
};
- mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
- context, currentBrightness);
- if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
- for (BrightnessClamper clamper: mClampers) {
- if (clamper.getType() == Type.POWER) {
- mPowerClamper = (BrightnessPowerClamper) clamper;
- break;
- }
- }
- }
- mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
- data);
+ mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListenerInternal,
+ data, currentBrightness);
mModifiers.forEach(m -> {
if (m instanceof DisplayDeviceDataListener l) {
@@ -151,7 +129,6 @@ public class BrightnessClamperController {
}
});
mOnPropertiesChangedListener = properties -> {
- mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged);
};
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
@@ -163,7 +140,6 @@ public class BrightnessClamperController {
*/
public void onDisplayChanged(DisplayDeviceData data) {
mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
- mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data));
adjustLightSensorSubscription();
}
@@ -175,56 +151,21 @@ public class BrightnessClamperController {
public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
DisplayManagerInternal.DisplayPowerRequest request,
float brightnessValue, boolean slowChange, int displayState) {
- float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
-
+ mDisplayState = displayState;
DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
displayBrightnessState);
builder.setIsSlowChange(slowChange);
- builder.setBrightness(cappedBrightness);
- builder.setMaxBrightness(mBrightnessCap);
- builder.setCustomAnimationRate(mCustomAnimationRate);
- builder.setBrightnessMaxReason(getBrightnessMaxReason());
- if (mClamperType != null) {
- builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
- if (!mClamperApplied) {
- builder.setIsSlowChange(false);
- }
- mClamperApplied = true;
- } else {
- mClamperApplied = false;
- }
+ builder.setBrightness(brightnessValue);
- if (displayState != STATE_ON) {
- mLightSensorController.stop();
- } else {
- adjustLightSensorSubscription();
- }
+ adjustLightSensorSubscription();
for (int i = 0; i < mModifiers.size(); i++) {
mModifiers.get(i).apply(request, builder);
}
- if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
- if (mPowerClamper != null) {
- mPowerClamper.updateCurrentBrightness(cappedBrightness);
- }
- }
-
return builder.build();
}
- @BrightnessInfo.BrightnessMaxReason
- private int getBrightnessMaxReason() {
- if (mClamperType == null) {
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
- } else if (mClamperType == Type.POWER) {
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
- } else {
- Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
- return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
- }
- }
-
/**
* Called when the user switches.
*/
@@ -237,13 +178,8 @@ public class BrightnessClamperController {
*/
public void dump(PrintWriter writer) {
writer.println("BrightnessClamperController:");
- writer.println("----------------------------");
- writer.println(" mBrightnessCap: " + mBrightnessCap);
- writer.println(" mClamperType: " + mClamperType);
- writer.println(" mClamperApplied: " + mClamperApplied);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mLightSensorController.dump(ipw);
- mClampers.forEach(clamper -> clamper.dump(ipw));
mModifiers.forEach(modifier -> modifier.dump(ipw));
}
@@ -252,41 +188,20 @@ public class BrightnessClamperController {
* Called in DisplayControllerHandler
*/
public void stop() {
+ mStarted = false;
mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
mOnPropertiesChangedListener);
mLightSensorController.stop();
- mClampers.forEach(BrightnessClamper::stop);
mModifiers.forEach(BrightnessStateModifier::stop);
}
// Called in DisplayControllerHandler
- private void recalculateBrightnessCap() {
- float brightnessCap = PowerManager.BRIGHTNESS_MAX;
- Type clamperType = null;
- float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-
- BrightnessClamper<?> minClamper = mClampers.stream()
- .filter(BrightnessClamper::isActive)
- .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
- clamper2.getBrightnessCap())).orElse(null);
-
- if (minClamper != null) {
- brightnessCap = minClamper.getBrightnessCap();
- clamperType = minClamper.getType();
- customAnimationRate = minClamper.getCustomAnimationRate();
- }
-
+ private void recalculateModifiersState() {
ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState();
- mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState));
-
- if (mBrightnessCap != brightnessCap
- || mClamperType != clamperType
- || mCustomAnimationRate != customAnimationRate
- || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
- mBrightnessCap = brightnessCap;
- mClamperType = clamperType;
- mCustomAnimationRate = customAnimationRate;
+ mStatefulModifiers.forEach((modifier) -> modifier.applyStateChange(newAggregatedState));
+
+ if (needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
mClamperChangeListenerExternal.onChanged();
}
mModifiersAggregatedState = newAggregatedState;
@@ -305,15 +220,17 @@ public class BrightnessClamperController {
}
private void start() {
- if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) {
+ if (!mDeviceConfigListeners.isEmpty()) {
mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
mExecutor, mOnPropertiesChangedListener);
}
adjustLightSensorSubscription();
+ mStarted = true;
}
private void adjustLightSensorSubscription() {
- if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ if (mDisplayState == STATE_ON && mModifiers.stream()
+ .anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
mLightSensorController.restart();
} else {
mLightSensorController.stop();
@@ -336,33 +253,25 @@ public class BrightnessClamperController {
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
}
- List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
- DisplayManagerFlags flags, Context context, float currentBrightness) {
- List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
-
- if (flags.isPowerThrottlingClamperEnabled()) {
- // Check if power-throttling config is present.
- PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
- if (configData != null) {
- clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
- data, currentBrightness));
- }
- }
- return clampers;
- }
-
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, ClamperChangeListener listener,
- DisplayDeviceData data) {
+ DisplayDeviceData data, float currentBrightness) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new BrightnessThermalModifier(handler, listener, data));
if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context,
listener, data));
}
+ if (flags.isPowerThrottlingClamperEnabled()) {
+ // Check if power-throttling config is present.
+ PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
+ if (configData != null) {
+ modifiers.add(new BrightnessPowerModifier(handler, listener,
+ data, currentBrightness));
+ }
+ }
- modifiers.add(new DisplayDimModifier(context));
+ modifiers.add(new DisplayDimModifier(data.mDisplayId, context));
modifiers.add(new BrightnessLowPowerModeModifier());
if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
@@ -393,7 +302,7 @@ public class BrightnessClamperController {
* Config Data for clampers/modifiers
*/
public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData,
- BrightnessPowerClamper.PowerData,
+ BrightnessPowerModifier.PowerData,
BrightnessWearBedtimeModeModifier.WearBedtimeModeData {
@NonNull
private final String mUniqueDisplayId;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index be8fa5a0f0ce..cbeb863971dd 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -16,6 +16,8 @@
package com.android.server.display.brightness.clamper;
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -50,10 +52,12 @@ abstract class BrightnessModifier implements BrightnessStateModifier {
}
if (!mApplied) {
stateBuilder.setIsSlowChange(false);
+ stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
}
mApplied = true;
} else if (mApplied) {
stateBuilder.setIsSlowChange(false);
+ stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
mApplied = false;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
index 1a18b004a3e2..146f2f0f0da9 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
@@ -22,6 +22,8 @@ import static com.android.server.display.brightness.clamper.BrightnessClamperCon
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -37,6 +39,7 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DeviceConfigParsingUtils;
@@ -48,16 +51,16 @@ import java.util.function.BiFunction;
import java.util.function.Function;
-class BrightnessPowerClamper extends
- BrightnessClamper<BrightnessPowerClamper.PowerData> {
+class BrightnessPowerModifier implements BrightnessStateModifier,
+ BrightnessClamperController.DisplayDeviceDataListener,
+ BrightnessClamperController.StatefulModifier,
+ BrightnessClamperController.DeviceConfigListener {
private static final String TAG = "BrightnessPowerClamper";
@NonNull
- private final Injector mInjector;
- @NonNull
private final DeviceConfigParameterProvider mConfigParameterProvider;
- @Nullable
- private PmicMonitor mPmicMonitor;
+ @NonNull
+ private final PmicMonitor mPmicMonitor;
// data from DeviceConfig, for all displays, for all dataSets
// mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
@NonNull
@@ -71,11 +74,9 @@ class BrightnessPowerClamper extends
@Nullable
private PowerThrottlingData mPowerThrottlingDataActive = null;
@Nullable
- private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+ private PowerThrottlingConfigData mPowerThrottlingConfigData;
@NonNull
private final ThermalLevelListener mThermalLevelListener;
- @NonNull
- private final PowerChangeListener mPowerChangeListener;
private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
private boolean mCurrentThermalLevelChanged = false;
private float mCurrentAvgPowerConsumed = 0;
@@ -100,28 +101,32 @@ class BrightnessPowerClamper extends
private final Function<List<ThrottlingLevel>, PowerThrottlingData>
mDataSetMapper = PowerThrottlingData::create;
+ protected final Handler mHandler;
+ protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
- BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;;
+ private boolean mApplied = false;
+
+
+ BrightnessPowerModifier(Handler handler, ClamperChangeListener listener,
PowerData powerData, float currentBrightness) {
this(new Injector(), handler, listener, powerData, currentBrightness);
}
@VisibleForTesting
- BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
- PowerData powerData, float currentBrightness) {
- super(handler, listener);
- mInjector = injector;
+ BrightnessPowerModifier(@NonNull Injector injector, Handler handler,
+ ClamperChangeListener listener, PowerData powerData, float currentBrightness) {
+ mHandler = handler;
+ mChangeListener = listener;
mCurrentBrightness = currentBrightness;
- mPowerChangeListener = (powerConsumed, thermalStatus) -> {
- recalculatePowerQuotaChange(powerConsumed, thermalStatus);
- };
+
mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
if (mPowerThrottlingConfigData != null) {
mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
}
mThermalLevelListener = new ThermalLevelListener(handler);
mPmicMonitor =
- mInjector.getPmicMonitor(mPowerChangeListener,
+ injector.getPmicMonitor(this::recalculatePowerQuotaChange,
mThermalLevelListener.getThermalService(),
mPowerThrottlingConfigData.pollingWindowMaxMillis,
mPowerThrottlingConfigData.pollingWindowMinMillis);
@@ -134,69 +139,91 @@ class BrightnessPowerClamper extends
});
}
- @VisibleForTesting
- PowerChangeListener getPowerChangeListener() {
- return mPowerChangeListener;
+ //region BrightnessStateModifier
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder) {
+ if (stateBuilder.getMaxBrightness() > mBrightnessCap) {
+ stateBuilder.setMaxBrightness(mBrightnessCap);
+ stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap));
+ stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC);
+ stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ // set custom animation rate only when modifier is activated.
+ // this will allow auto brightness to apply slow change even when modifier is active
+ if (!mApplied) {
+ stateBuilder.setCustomAnimationRate(mCustomAnimationRateSec);
+ mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ }
+ mApplied = true;
+ } else {
+ mApplied = false;
+ }
+ mCurrentBrightness = stateBuilder.getBrightness();
}
@Override
- @NonNull
- BrightnessClamper.Type getType() {
- return Type.POWER;
+ public void stop() {
+ mPmicMonitor.shutdown();
+ mThermalLevelListener.stop();
+ }
+
+ /**
+ * Dumps the state of BrightnessPowerClamper.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessPowerClamper:");
+ pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
+ pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
+ pw.println(" mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
+ pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+ : mPowerThrottlingDataFromDDC.toString()));
+ pw.println(" mBrightnessCap: " + mBrightnessCap);
+ pw.println(" mApplied: " + mApplied);
+ mThermalLevelListener.dump(pw);
}
@Override
- float getCustomAnimationRate() {
- return mCustomAnimationRateSec;
+ public boolean shouldListenToLightSensor() {
+ return false;
}
@Override
- void onDeviceConfigChanged() {
- mHandler.post(() -> {
- loadOverrideData();
- recalculateActiveData();
- });
+ public void setAmbientLux(float lux) {
+ // noop
}
+ //endregion
+ //region DisplayDeviceDataListener
@Override
- void onDisplayChanged(PowerData data) {
+ public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) {
mHandler.post(() -> {
setDisplayData(data);
recalculateActiveData();
});
}
+ //endregion
+ //region StatefulModifier
@Override
- void stop() {
- if (mPmicMonitor != null) {
- mPmicMonitor.shutdown();
- }
- if (mThermalLevelListener != null) {
- mThermalLevelListener.stop();
+ public void applyStateChange(
+ BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+ if (aggregatedState.mMaxBrightness > mBrightnessCap) {
+ aggregatedState.mMaxBrightness = mBrightnessCap;
+ aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
}
}
+ //endregion
- /**
- * Dumps the state of BrightnessPowerClamper.
- */
- public void dump(PrintWriter pw) {
- pw.println("BrightnessPowerClamper:");
- pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
- pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
- pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
- pw.println(" mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
- pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
- : mPowerThrottlingDataFromDDC.toString()));
- mThermalLevelListener.dump(pw);
- super.dump(pw);
- }
-
- /**
- * Updates current brightness, for power calculations.
- */
- public void updateCurrentBrightness(float currentBrightness) {
- mCurrentBrightness = currentBrightness;
+ //region DeviceConfigListener
+ @Override
+ public void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
}
+ //endregion
private void recalculateActiveData() {
if (mUniqueDisplayId == null || mDataId == null) {
@@ -206,9 +233,7 @@ class BrightnessPowerClamper extends
.getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
mPowerThrottlingDataFromDDC);
if (mPowerThrottlingDataActive == null) {
- if (mPmicMonitor != null) {
- mPmicMonitor.stop();
- }
+ mPmicMonitor.stop();
}
}
@@ -231,7 +256,6 @@ class BrightnessPowerClamper extends
}
private void recalculateBrightnessCap() {
- boolean isActive = false;
float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
if (mPowerThrottlingDataActive == null) {
@@ -240,7 +264,6 @@ class BrightnessPowerClamper extends
if (powerQuota > 0) {
if (BrightnessUtils.isValidBrightnessValue(mCurrentBrightness)
&& (mCurrentAvgPowerConsumed > powerQuota)) {
- isActive = true;
// calculate new brightness Cap.
// Brightness has a linear relation to power-consumed.
targetBrightnessCap =
@@ -248,11 +271,9 @@ class BrightnessPowerClamper extends
} else if (mCurrentThermalLevelChanged) {
if (mCurrentThermalLevel == Temperature.THROTTLING_NONE) {
// reset pmic and remove the power-throttling cap.
- isActive = true;
targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
mPmicMonitor.stop();
} else {
- isActive = true;
// Since the thermal status has changed, we need to remove power-throttling cap.
// Instead of recalculating and changing brightness again, adding flicker,
// we will wait for the next pmic cycle to re-evaluate this value
@@ -263,7 +284,6 @@ class BrightnessPowerClamper extends
}
}
} else { // Current power consumed is under the quota.
- isActive = true;
targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
}
}
@@ -274,8 +294,7 @@ class BrightnessPowerClamper extends
mPowerThrottlingConfigData.brightnessLowestCapAllowed);
}
- if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
- mIsActive = isActive;
+ if (mBrightnessCap != targetBrightnessCap) {
Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+ mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
+ " for current screen brightness: " + mCurrentBrightness + "\n"
@@ -309,11 +328,7 @@ class BrightnessPowerClamper extends
private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
mHandler.post(() -> {
- if (mCurrentThermalLevel != thermalStatus) {
- mCurrentThermalLevelChanged = true;
- } else {
- mCurrentThermalLevelChanged = false;
- }
+ mCurrentThermalLevelChanged = mCurrentThermalLevel != thermalStatus;
mCurrentThermalLevel = thermalStatus;
mCurrentAvgPowerConsumed = avgPowerConsumed;
recalculateBrightnessCap();
@@ -398,7 +413,7 @@ class BrightnessPowerClamper extends
@Temperature.ThrottlingStatus int status = temp.getStatus();
if (status >= Temperature.THROTTLING_LIGHT) {
Slog.d(TAG, "Activating pmic monitor due to thermal state:" + status);
- mHandler.post(() -> activatePmicMonitor());
+ mHandler.post(BrightnessPowerModifier.this::activatePmicMonitor);
} else {
if (!mPmicMonitor.isStopped()) {
mHandler.post(() -> deactivatePmicMonitor(status));
@@ -452,6 +467,7 @@ class BrightnessPowerClamper extends
@VisibleForTesting
static class Injector {
+ @NonNull
PmicMonitor getPmicMonitor(PowerChangeListener powerChangeListener,
IThermalService thermalService,
int pollingMaxTimeMillis,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index ab880bf28743..0237af338b18 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -38,12 +38,12 @@ class DisplayDimModifier extends BrightnessModifier {
// mScreenBrightnessDimConfig.
private final float mScreenBrightnessMinimumDimAmount;
- DisplayDimModifier(Context context) {
+ DisplayDimModifier(int displayId, Context context) {
PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
Resources resources = context.getResources();
mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+ pm.getBrightnessConstraint(displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
mScreenBrightnessMinimumDimAmount = resources.getFloat(
R.dimen.config_screenBrightnessMinimumDimAmountFloat);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
index 355f4fe65279..3b748d719784 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -16,7 +16,7 @@
package com.android.server.display.brightness.clamper;
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
import android.annotation.Nullable;
import android.hardware.power.stats.EnergyConsumer;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 45106f54cb9f..585fc44fa452 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -256,6 +256,10 @@ public class DisplayManagerFlags {
Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS,
Flags::displayListenerPerformanceImprovements
);
+ private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState(
+ Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
+ Flags::enableDisplayContentModeManagement
+ );
private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
@@ -556,6 +560,10 @@ public class DisplayManagerFlags {
return mDisplayListenerPerformanceImprovementsFlagState.isEnabled();
}
+ public boolean isDisplayContentModeManagementEnabled() {
+ return mEnableDisplayContentModeManagementFlagState.isEnabled();
+ }
+
/**
* @return {@code true} if the flag for subscribing to granular display events is enabled
*/
@@ -618,6 +626,7 @@ public class DisplayManagerFlags {
pw.println(" " + mEnablePluginManagerFlagState);
pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
pw.println(" " + mSubscribeGranularDisplayEvents);
+ pw.println(" " + mEnableDisplayContentModeManagementFlagState);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3976d01d806d..123b7dfbf843 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,6 +9,7 @@ flag {
description: "Allows querying of AOD availability"
bug: "324046664"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -267,6 +268,7 @@ flag {
description: "Feature flag for an API to get the highest defined HDR/SDR ratio for a display."
bug: "335181559"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -429,6 +431,7 @@ flag {
description: "Flag for an API to get whether display supports ARR or not"
bug: "361433651"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -445,6 +448,7 @@ flag {
description: "Flag for an API to get suggested frame rates"
bug: "361433796"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -453,6 +457,7 @@ flag {
description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
bug: "372700957"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -461,6 +466,7 @@ flag {
description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
bug: "365163968"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/display/plugin/PluginEventStorage.java b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
new file mode 100644
index 000000000000..c58ba556bcb6
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
@@ -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.server.display.plugin;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.util.RingBuffer;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+class PluginEventStorage {
+ private static final long TIME_FRAME_LENGTH = 60_000;
+ private static final long MIN_EVENT_DELAY = 500;
+ private static final int MAX_TIME_FRAMES = 10;
+ // not thread safe
+ private static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
+ "MM-dd HH:mm:ss.SSS", Locale.US);
+
+ RingBuffer<TimeFrame> mEvents = new RingBuffer<>(
+ TimeFrame::new, TimeFrame[]::new, MAX_TIME_FRAMES);
+
+ private final Map<PluginType<?>, Long> mEventTimes = new HashMap<>();
+ private long mTimeFrameStart = 0;
+ private final Map<PluginType<?>, EventCounter> mCounters = new HashMap<>();
+
+ <T> void onValueUpdated(PluginType<T> type) {
+ long eventTime = System.currentTimeMillis();
+ if (eventTime - TIME_FRAME_LENGTH > mTimeFrameStart) { // event is in next TimeFrame
+ closeCurrentTimeFrame();
+ mTimeFrameStart = eventTime;
+ }
+ updateCurrentTimeFrame(type, eventTime);
+ }
+
+ private void closeCurrentTimeFrame() {
+ if (!mCounters.isEmpty()) {
+ mEvents.append(new TimeFrame(
+ mTimeFrameStart, mTimeFrameStart + TIME_FRAME_LENGTH, mCounters));
+ mCounters.clear();
+ }
+ }
+
+ private <T> void updateCurrentTimeFrame(PluginType<T> type, long eventTime) {
+ EventCounter counter = mCounters.get(type);
+ long previousTimestamp = mEventTimes.getOrDefault(type, 0L);
+ if (counter == null) {
+ counter = new EventCounter();
+ mCounters.put(type, counter);
+ }
+ counter.increase(eventTime, previousTimestamp);
+ mEventTimes.put(type, eventTime);
+ }
+
+ List<TimeFrame> getTimeFrames() {
+ List<TimeFrame> timeFrames = new ArrayList<>(Arrays.stream(mEvents.toArray()).toList());
+ timeFrames.add(new TimeFrame(
+ mTimeFrameStart, System.currentTimeMillis(), mCounters));
+ return timeFrames;
+ }
+
+ static class TimeFrame {
+ private final long mStart;
+ private final long mEnd;
+ private final Map<PluginType<?>, EventCounter> mCounters;
+
+ private TimeFrame() {
+ this(0, 0, Map.of());
+ }
+
+ private TimeFrame(long start, long end, Map<PluginType<?>, EventCounter> counters) {
+ mStart = start;
+ mEnd = end;
+ mCounters = new HashMap<>(counters);
+ }
+
+ @SuppressWarnings("JavaUtilDate")
+ void dump(PrintWriter pw) {
+ pw.append("TimeFrame:[")
+ .append(sDateFormat.format(new Date(mStart)))
+ .append(" - ")
+ .append(sDateFormat.format(new Date(mEnd)))
+ .println("]:");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ if (mCounters.isEmpty()) {
+ ipw.println("NO EVENTS");
+ } else {
+ for (Map.Entry<PluginType<?>, EventCounter> entry: mCounters.entrySet()) {
+ ipw.append(entry.getKey().mName).append(" -> {");
+ entry.getValue().dump(ipw);
+ ipw.println("}");
+ }
+ }
+ }
+ }
+
+ private static class EventCounter {
+ private int mEventCounter = 0;
+ private int mFastEventCounter = 0;
+
+ private void increase(long timestamp, long previousTimestamp) {
+ mEventCounter++;
+ if (timestamp - previousTimestamp < MIN_EVENT_DELAY) {
+ mFastEventCounter++;
+ }
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.append("Count:").append(String.valueOf(mEventCounter))
+ .append("; Fast:").append(String.valueOf(mFastEventCounter));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java
index 2bcea777e681..dd3415fb614d 100644
--- a/services/core/java/com/android/server/display/plugin/PluginStorage.java
+++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java
@@ -25,6 +25,7 @@ import com.android.tools.r8.keepanno.annotations.KeepForApi;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,6 +40,8 @@ public class PluginStorage {
private final Map<PluginType<?>, Object> mValues = new HashMap<>();
@GuardedBy("mLock")
private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
+ @GuardedBy("mLock")
+ private final PluginEventStorage mPluginEventStorage = new PluginEventStorage();
/**
* Updates value in storage and forwards it to corresponding listeners.
@@ -50,6 +53,7 @@ public class PluginStorage {
Set<PluginManager.PluginChangeListener<T>> localListeners;
synchronized (mLock) {
mValues.put(type, value);
+ mPluginEventStorage.onValueUpdated(type);
ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
localListeners = new LinkedHashSet<>(container.mListeners);
}
@@ -91,13 +95,19 @@ public class PluginStorage {
Map<PluginType<?>, Object> localValues;
@SuppressWarnings("rawtypes")
Map<PluginType, Set> localListeners = new HashMap<>();
+ List<PluginEventStorage.TimeFrame> timeFrames;
synchronized (mLock) {
+ timeFrames = mPluginEventStorage.getTimeFrames();
localValues = new HashMap<>(mValues);
mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners));
}
pw.println("PluginStorage:");
pw.println("values=" + localValues);
pw.println("listeners=" + localListeners);
+ pw.println("PluginEventStorage:");
+ for (PluginEventStorage.TimeFrame timeFrame: timeFrames) {
+ timeFrame.dump(pw);
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 53c02176f11d..102de73374b7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.media.tv.flags.Flags.hdmiControlCollectPhysicalAddress;
+
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
@@ -35,6 +37,8 @@ public class HdmiCecAtomWriter {
@VisibleForTesting
protected static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
private static final int ERROR_CODE_UNKNOWN = -1;
+ @VisibleForTesting
+ protected static final int PHYSICAL_ADDRESS_INVALID = 0xFFFF;
/**
* Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
@@ -95,6 +99,11 @@ public class HdmiCecAtomWriter {
return createUserControlPressedSpecialArgs(message);
case Constants.MESSAGE_FEATURE_ABORT:
return createFeatureAbortSpecialArgs(message);
+ case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ if (hdmiControlCollectPhysicalAddress()) {
+ return createReportPhysicalAddressSpecialArgs(message);
+ }
+ return new MessageReportedSpecialArgs();
default:
return new MessageReportedSpecialArgs();
}
@@ -140,6 +149,23 @@ public class HdmiCecAtomWriter {
}
/**
+ * Constructs the special arguments for a <Report Physical Address> message.
+ *
+ * @param message The HDMI CEC message to log
+ */
+ private MessageReportedSpecialArgs createReportPhysicalAddressSpecialArgs(
+ HdmiCecMessage message) {
+ MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
+
+ if (message.getParams().length > 1) {
+ int physicalAddress = (message.getParams()[0] << 8) | message.getParams()[1];
+ specialArgs.mPhysicalAddress = physicalAddress;
+ }
+
+ return specialArgs;
+ }
+
+ /**
* Writes a HdmiCecMessageReported atom.
*
* @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
@@ -156,7 +182,8 @@ public class HdmiCecAtomWriter {
genericArgs.mSendMessageResult,
specialArgs.mUserControlPressedCommand,
specialArgs.mFeatureAbortOpcode,
- specialArgs.mFeatureAbortReason);
+ specialArgs.mFeatureAbortReason,
+ specialArgs.mPhysicalAddress);
}
/**
@@ -166,7 +193,7 @@ public class HdmiCecAtomWriter {
protected void writeHdmiCecMessageReportedAtom(int uid, int direction,
int initiatorLogicalAddress, int destinationLogicalAddress, int opcode,
int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode,
- int featureAbortReason) {
+ int featureAbortReason, int physicalAddress) {
FrameworkStatsLog.write(
FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
uid,
@@ -177,7 +204,8 @@ public class HdmiCecAtomWriter {
sendMessageResult,
userControlPressedCommand,
featureAbortOpcode,
- featureAbortReason);
+ featureAbortReason,
+ physicalAddress);
}
/**
@@ -237,6 +265,26 @@ public class HdmiCecAtomWriter {
enumLogReason);
}
+ /**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ * @param isEnabled Whether the setting is enabled.
+ * @param enumLogReason The event that triggered the log.
+ * @param manufacturerPnpId Manufacturer PNP ID reported in the EDID.
+ * @param manufacturerYear Manufacture year reported in the EDID.
+ * @param manufacturerWeek Manufacture week reporter in the EDID.
+ */
+ public void powerStateChangeOnActiveSourceLostChanged(boolean isEnabled, int enumLogReason,
+ String manufacturerPnpId, int manufacturerYear, int manufacturerWeek) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLED,
+ isEnabled,
+ enumLogReason,
+ manufacturerPnpId,
+ manufacturerYear,
+ manufacturerWeek);
+ }
+
private int earcStateToEnum(int earcState) {
switch (earcState) {
case HDMI_EARC_STATUS_IDLE:
@@ -284,5 +332,6 @@ public class HdmiCecAtomWriter {
int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
+ int mPhysicalAddress = PHYSICAL_ADDRESS_INVALID;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 1b527daafd24..0b667fc10880 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -21,6 +21,7 @@ import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -31,6 +32,7 @@ import android.os.PowerManager;
import android.os.SystemProperties;
import android.sysprop.HdmiProperties;
import android.util.Slog;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
@@ -82,6 +84,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// lost.
private Handler mDelayedPopupOnActiveSourceLostHandler;
+ private boolean mIsActiveSourceLostPopupLaunched;
+
// Determines what action should be taken upon receiving Routing Control messages.
@VisibleForTesting
protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -96,6 +100,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
mDelayedStandbyOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mDelayedPopupOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+ mIsActiveSourceLostPopupLaunched = false;
}
@Override
@@ -275,6 +280,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
public void run() {
if (!isActiveSource()) {
mService.standby();
+ mIsActiveSourceLostPopupLaunched = false;
}
}
}
@@ -283,6 +289,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
void dismissUiOnActiveSourceStatusRecovered() {
assertRunOnServiceThread();
Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
+ mIsActiveSourceLostPopupLaunched = false;
mService.sendBroadcastAsUser(intent);
}
@@ -516,6 +523,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(intent, context.getUser());
+ mIsActiveSourceLostPopupLaunched = true;
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Unable to start HdmiCecActiveSourceLostActivity");
} finally {
@@ -733,6 +741,14 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
return Constants.ADDR_TV;
}
+ boolean isActiveSourceLostPopupLaunched() {
+ return mIsActiveSourceLostPopupLaunched;
+ }
+
+ void setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched) {
+ mIsActiveSourceLostPopupLaunched = isActiveSourceLostPopupLaunched;
+ }
+
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6e98bff8dda5..35ef18b144e7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -50,6 +50,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayManager;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
@@ -106,6 +107,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -725,6 +727,13 @@ public class HdmiControlService extends SystemService {
}
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
setProhibitMode(false);
+ if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+ Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+ + " mode.");
+ mHdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_ENABLED);
+ setWasCecDisabledOnStandbyByLowEnergyMode(false);
+ }
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
@@ -771,14 +780,6 @@ public class HdmiControlService extends SystemService {
Slog.i(TAG, "Device does not support eARC.");
}
mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
- if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
- Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
- + " mode.");
- getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
- HDMI_CEC_CONTROL_ENABLED);
- setWasCecDisabledOnStandbyByLowEnergyMode(false);
- setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
- }
if (isCecControlEnabled()) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
@@ -1008,6 +1009,21 @@ public class HdmiControlService extends SystemService {
}
}, mServiceThreadExecutor);
+ if (isPlaybackDevice()) {
+ mHdmiCecConfig.registerChangeListener(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ new HdmiCecConfig.SettingChangeListener() {
+ @Override
+ public void onChange(String setting) {
+ boolean goToStandbyOnActiveSourceLost =
+ mHdmiCecConfig.getStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)
+ .equals(HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ writePowerStateChangeOnActiveSourceLostAtom(goToStandbyOnActiveSourceLost);
+ }
+ }, mServiceThreadExecutor);
+ }
+
mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -3190,6 +3206,7 @@ public class HdmiControlService extends SystemService {
// Cancel an existing timer to send the device to sleep since OTP was triggered.
playback().mDelayedStandbyOnActiveSourceLostHandler
.removeCallbacksAndMessages(null);
+ playback().setIsActiveSourceLostPopupLaunched(false);
}
if (source == null) {
@@ -5228,6 +5245,34 @@ public class HdmiControlService extends SystemService {
}
/**
+ * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+ * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+ */
+ protected void writePowerStateChangeOnActiveSourceLostAtom(boolean isSettingEnabled) {
+ String manufacturerPnpId = "undefined";
+ int manufactureYear = -1;
+ int manufactureWeek = -1;
+ Display display = getContext().getDisplay();
+ if (display != null) {
+ DeviceProductInfo deviceProductInfo = display.getDeviceProductInfo();
+ manufacturerPnpId = deviceProductInfo.getManufacturerPnpId();
+ manufactureYear = deviceProductInfo.getManufactureYear();
+ }
+ int enumLogReason =
+ HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_UNKNOWN;
+ if (playback() != null) {
+ if (playback().isActiveSourceLostPopupLaunched()) {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP;
+ } else {
+ enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING;
+ }
+ }
+
+ getAtomWriter().powerStateChangeOnActiveSourceLostChanged(isSettingEnabled, enumLogReason,
+ manufacturerPnpId, manufactureYear, manufactureWeek);
+ }
+
+ /**
* Reads the property that checks if CEC was enabled by the user while in offline mode such that
* it won't be disabled when going to sleep by low energy mode.
*/
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 35b3673fdf77..9a6c87e525e0 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -324,16 +324,12 @@ class PendingReports {
// Allow system apps to skip the consent dialog and use their in-built consent mechanism
// instead.
- boolean captureConsentlessBugreportDelegatedConsentGranted = false;
- if ((flags & IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT) != 0) {
- captureConsentlessBugreportDelegatedConsentGranted =
- mPermissionManager.checkPermissionForDataDelivery(
- Manifest.permission
- .CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT,
- attributionSource,
- /* message= */ null)
- == PERMISSION_GRANTED;
- }
+ boolean captureConsentlessBugreportDelegatedConsentGranted =
+ mPermissionManager.checkPermissionForDataDelivery(
+ Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT,
+ attributionSource,
+ /* message= */ null)
+ == PERMISSION_GRANTED;
if (captureConsentlessBugreportOnUserdebugBuildGranted
|| captureConsentlessBugreportDelegatedConsentGranted) {
diff --git a/services/core/java/com/android/server/input/InputDataStore.java b/services/core/java/com/android/server/input/InputDataStore.java
new file mode 100644
index 000000000000..e8f21fe8fb74
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputDataStore.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright 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.input;
+
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages persistent state recorded by the input manager service as a set of XML files.
+ * Caller must acquire lock on the data store before accessing it.
+ */
+public final class InputDataStore {
+ private static final String TAG = "InputDataStore";
+
+ private static final String INPUT_MANAGER_DIRECTORY = "input";
+
+ private static final String TAG_ROOT = "root";
+
+ private static final String TAG_INPUT_GESTURE_LIST = "input_gesture_list";
+ private static final String TAG_INPUT_GESTURE = "input_gesture";
+ private static final String TAG_KEY_TRIGGER = "key_trigger";
+ private static final String TAG_TOUCHPAD_TRIGGER = "touchpad_trigger";
+ private static final String TAG_APP_LAUNCH_DATA = "app_launch_data";
+
+ private static final String ATTR_KEY_TRIGGER_KEYCODE = "keycode";
+ private static final String ATTR_KEY_TRIGGER_MODIFIER_STATE = "modifiers";
+ private static final String ATTR_KEY_GESTURE_TYPE = "key_gesture_type";
+ private static final String ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE = "touchpad_gesture_type";
+ private static final String ATTR_APP_LAUNCH_DATA_CATEGORY = "category";
+ private static final String ATTR_APP_LAUNCH_DATA_ROLE = "role";
+ private static final String ATTR_APP_LAUNCH_DATA_PACKAGE_NAME = "package_name";
+ private static final String ATTR_APP_LAUNCH_DATA_CLASS_NAME = "class_name";
+
+ private final FileInjector mInputGestureFileInjector;
+
+ public InputDataStore() {
+ this(new FileInjector("input_gestures.xml"));
+ }
+
+ public InputDataStore(final FileInjector inputGestureFileInjector) {
+ mInputGestureFileInjector = inputGestureFileInjector;
+ }
+
+ /**
+ * Reads from the local disk storage the list of customized input gestures.
+ *
+ * @param userId The user id to fetch the gestures for.
+ * @return List of {@link InputGestureData} which the user previously customized.
+ */
+ public List<InputGestureData> loadInputGestures(int userId) {
+ List<InputGestureData> inputGestureDataList;
+ try {
+ final InputStream inputStream = mInputGestureFileInjector.openRead(userId);
+ inputGestureDataList = readInputGesturesXml(inputStream, false);
+ inputStream.close();
+ } catch (IOException exception) {
+ // In case we are unable to read from the file on disk or another IO operation error,
+ // fail gracefully.
+ Slog.e(TAG, "Failed to read from " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), exception);
+ return List.of();
+ } catch (Exception exception) {
+ // In the case of any other exception, we want it to bubble up as this would be due
+ // to malformed trusted XML data.
+ throw new RuntimeException(
+ "Failed to read from " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), exception);
+ }
+ return inputGestureDataList;
+ }
+
+ /**
+ * Writes to the local disk storage the list of customized input gestures provided as a param.
+ *
+ * @param userId The user id to store the {@link InputGestureData} list under.
+ * @param inputGestureDataList The list of custom input gestures for the given {@code userId}.
+ */
+ public void saveInputGestures(int userId, List<InputGestureData> inputGestureDataList) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mInputGestureFileInjector.startWrite(userId);
+ writeInputGestureXml(outputStream, false, inputGestureDataList);
+ mInputGestureFileInjector.finishWrite(userId, outputStream, true);
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to write to file " + mInputGestureFileInjector.getAtomicFileForUserId(
+ userId), e);
+ mInputGestureFileInjector.finishWrite(userId, outputStream, false);
+ }
+ }
+
+ @VisibleForTesting
+ List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded)
+ throws XmlPullParserException, IOException {
+ List<InputGestureData> inputGestureDataList = new ArrayList<>();
+ TypedXmlPullParser parser;
+ if (utf8Encoded) {
+ parser = Xml.newFastPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ } else {
+ parser = Xml.resolvePullParser(stream);
+ }
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String tag = parser.getName();
+ if (TAG_ROOT.equals(tag)) {
+ continue;
+ }
+
+ if (TAG_INPUT_GESTURE_LIST.equals(tag)) {
+ inputGestureDataList.addAll(readInputGestureListFromXml(parser));
+ }
+ }
+ return inputGestureDataList;
+ }
+
+ private InputGestureData readInputGestureFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException, IllegalArgumentException {
+ InputGestureData.Builder builder = new InputGestureData.Builder();
+ builder.setKeyGestureType(parser.getAttributeInt(null, ATTR_KEY_GESTURE_TYPE));
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ // If the parser has left the initial scope when it was called, break out.
+ if (outerDepth > parser.getDepth()) {
+ throw new RuntimeException(
+ "Parser has left the initial scope of the tag that was being parsed on "
+ + "line number: "
+ + parser.getLineNumber());
+ }
+
+ // If the parser has reached the closing tag for the Input Gesture, break out.
+ if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_INPUT_GESTURE)) {
+ break;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String tag = parser.getName();
+ if (TAG_KEY_TRIGGER.equals(tag)) {
+ builder.setTrigger(InputGestureData.createKeyTrigger(
+ parser.getAttributeInt(null, ATTR_KEY_TRIGGER_KEYCODE),
+ parser.getAttributeInt(null, ATTR_KEY_TRIGGER_MODIFIER_STATE)));
+ } else if (TAG_TOUCHPAD_TRIGGER.equals(tag)) {
+ builder.setTrigger(InputGestureData.createTouchpadTrigger(
+ parser.getAttributeInt(null, ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE)));
+ } else if (TAG_APP_LAUNCH_DATA.equals(tag)) {
+ final String roleValue = parser.getAttributeValue(null, ATTR_APP_LAUNCH_DATA_ROLE);
+ final String categoryValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_CATEGORY);
+ final String classNameValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_CLASS_NAME);
+ final String packageNameValue = parser.getAttributeValue(null,
+ ATTR_APP_LAUNCH_DATA_PACKAGE_NAME);
+ final AppLaunchData appLaunchData = AppLaunchData.createLaunchData(categoryValue,
+ roleValue, packageNameValue, classNameValue);
+ if (appLaunchData != null) {
+ builder.setAppLaunchData(appLaunchData);
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ private List<InputGestureData> readInputGestureListFromXml(TypedXmlPullParser parser) throws
+ XmlPullParserException, IOException {
+ List<InputGestureData> inputGestureDataList = new ArrayList<>();
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ // If the parser has left the initial scope when it was called, break out.
+ if (outerDepth > parser.getDepth()) {
+ throw new RuntimeException(
+ "Parser has left the initial scope of the tag that was being parsed on "
+ + "line number: "
+ + parser.getLineNumber());
+ }
+
+ // If the parser has reached the closing tag for the Input Gesture List, break out.
+ if (type == XmlPullParser.END_TAG && parser.getName().equals(TAG_INPUT_GESTURE_LIST)) {
+ break;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String tag = parser.getName();
+ if (TAG_INPUT_GESTURE.equals(tag)) {
+ try {
+ inputGestureDataList.add(readInputGestureFromXml(parser));
+ } catch (IllegalArgumentException exception) {
+ Slog.w(TAG, "Invalid parameters for input gesture data: ", exception);
+ continue;
+ }
+ }
+ }
+ return inputGestureDataList;
+ }
+
+ @VisibleForTesting
+ void writeInputGestureXml(OutputStream stream, boolean utf8Encoded,
+ List<InputGestureData> inputGestureDataList) throws IOException {
+ final TypedXmlSerializer serializer;
+ if (utf8Encoded) {
+ serializer = Xml.newFastSerializer();
+ serializer.setOutput(stream, StandardCharsets.UTF_8.name());
+ } else {
+ serializer = Xml.resolveSerializer(stream);
+ }
+
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_ROOT);
+ writeInputGestureListToXml(serializer, inputGestureDataList);
+ serializer.endTag(null, TAG_ROOT);
+ serializer.endDocument();
+ }
+
+ private void writeInputGestureToXml(TypedXmlSerializer serializer,
+ InputGestureData inputGestureData) throws IOException {
+ serializer.startTag(null, TAG_INPUT_GESTURE);
+ serializer.attributeInt(null, ATTR_KEY_GESTURE_TYPE,
+ inputGestureData.getAction().keyGestureType());
+
+ final InputGestureData.Trigger trigger = inputGestureData.getTrigger();
+ if (trigger instanceof InputGestureData.KeyTrigger keyTrigger) {
+ serializer.startTag(null, TAG_KEY_TRIGGER);
+ serializer.attributeInt(null, ATTR_KEY_TRIGGER_KEYCODE, keyTrigger.getKeycode());
+ serializer.attributeInt(null, ATTR_KEY_TRIGGER_MODIFIER_STATE,
+ keyTrigger.getModifierState());
+ serializer.endTag(null, TAG_KEY_TRIGGER);
+ } else if (trigger instanceof InputGestureData.TouchpadTrigger touchpadTrigger) {
+ serializer.startTag(null, TAG_TOUCHPAD_TRIGGER);
+ serializer.attributeInt(null, ATTR_TOUCHPAD_TRIGGER_GESTURE_TYPE,
+ touchpadTrigger.getTouchpadGestureType());
+ serializer.endTag(null, TAG_TOUCHPAD_TRIGGER);
+ }
+
+ if (inputGestureData.getAction().appLaunchData() != null) {
+ serializer.startTag(null, TAG_APP_LAUNCH_DATA);
+ final AppLaunchData appLaunchData = inputGestureData.getAction().appLaunchData();
+ if (appLaunchData instanceof AppLaunchData.RoleData roleData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_ROLE, roleData.getRole());
+ } else if (appLaunchData
+ instanceof AppLaunchData.CategoryData categoryData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_CATEGORY,
+ categoryData.getCategory());
+ } else if (appLaunchData instanceof AppLaunchData.ComponentData componentData) {
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_PACKAGE_NAME,
+ componentData.getPackageName());
+ serializer.attribute(null, ATTR_APP_LAUNCH_DATA_CLASS_NAME,
+ componentData.getClassName());
+ }
+ serializer.endTag(null, TAG_APP_LAUNCH_DATA);
+ }
+
+ serializer.endTag(null, TAG_INPUT_GESTURE);
+ }
+
+ private void writeInputGestureListToXml(TypedXmlSerializer serializer,
+ List<InputGestureData> inputGestureDataList) throws IOException {
+ serializer.startTag(null, TAG_INPUT_GESTURE_LIST);
+ for (final InputGestureData inputGestureData : inputGestureDataList) {
+ writeInputGestureToXml(serializer, inputGestureData);
+ }
+ serializer.endTag(null, TAG_INPUT_GESTURE_LIST);
+ }
+
+ @VisibleForTesting
+ static class FileInjector {
+ private final SparseArray<AtomicFile> mAtomicFileMap = new SparseArray<>();
+ private final String mFileName;
+
+ FileInjector(String fileName) {
+ mFileName = fileName;
+ }
+
+ InputStream openRead(int userId) throws FileNotFoundException {
+ return getAtomicFileForUserId(userId).openRead();
+ }
+
+ FileOutputStream startWrite(int userId) throws IOException {
+ return getAtomicFileForUserId(userId).startWrite();
+ }
+
+ void finishWrite(int userId, FileOutputStream os, boolean success) {
+ if (success) {
+ getAtomicFileForUserId(userId).finishWrite(os);
+ } else {
+ getAtomicFileForUserId(userId).failWrite(os);
+ }
+ }
+
+ AtomicFile getAtomicFileForUserId(int userId) {
+ if (!mAtomicFileMap.contains(userId)) {
+ mAtomicFileMap.put(userId, new AtomicFile(new File(
+ Environment.buildPath(Environment.getDataSystemDeDirectory(userId),
+ INPUT_MANAGER_DIRECTORY), mFileName)));
+ }
+ return mAtomicFileMap.get(userId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
deleted file mode 100644
index a646d1e9bcb0..000000000000
--- a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.input;
-
-import android.sysprop.InputProperties;
-
-import java.util.Optional;
-
-/**
- * A component of {@link InputManagerService} responsible for managing the input sysprop flags
- *
- * @hide
- */
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-public final class InputFeatureFlagProvider {
-
- // To disable Keyboard backlight control via Framework, run:
- // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
- private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
- InputProperties.enable_keyboard_backlight_control().orElse(true);
-
- // To disable Framework controlled keyboard backlight animation run:
- // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
- private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED =
- InputProperties.enable_keyboard_backlight_animation().orElse(false);
-
- // To disable Custom keyboard backlight levels support via IDC files run:
- // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
- // restart)
- private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
- InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
-
- // To disable als based ambient keyboard backlight control run:
- // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
- // restart)
- private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
- InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
-
- private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
- private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
- private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
- private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
-
- public static boolean isKeyboardBacklightControlEnabled() {
- return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
- }
-
- public static boolean isKeyboardBacklightAnimationEnabled() {
- return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
- }
-
- public static boolean isKeyboardBacklightCustomLevelsEnabled() {
- return sKeyboardBacklightCustomLevelsOverride.orElse(
- KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
- }
-
- public static boolean isAmbientKeyboardBacklightControlEnabled() {
- return sAmbientKeyboardBacklightControlOverride.orElse(
- AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
- }
-
- public static void setKeyboardBacklightControlEnabled(boolean enabled) {
- sKeyboardBacklightControlOverride = Optional.of(enabled);
- }
-
- public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
- sKeyboardBacklightAnimationOverride = Optional.of(enabled);
- }
-
- public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) {
- sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
- }
-
- public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
- sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
- }
-
- /**
- * Clears all input feature flag overrides.
- */
- public static void clearOverrides() {
- sKeyboardBacklightControlOverride = Optional.empty();
- sKeyboardBacklightAnimationOverride = Optional.empty();
- sKeyboardBacklightCustomLevelsOverride = Optional.empty();
- sAmbientKeyboardBacklightControlOverride = Optional.empty();
- }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 265e4531a10a..bc44fed21f2d 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.graphics.PointF;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
@@ -47,6 +48,12 @@ public abstract class InputManagerInternal {
public abstract void setDisplayViewports(List<DisplayViewport> viewports);
/**
+ * Called by {@link com.android.server.display.DisplayManagerService} to inform InputManager
+ * about changes in the displays topology.
+ */
+ public abstract void setDisplayTopology(DisplayTopology topology);
+
+ /**
* Called by the power manager to tell the input manager whether it should start
* watching for wake events on given displays.
*
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1adf1c99024a..aee5e7f52c5f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@ import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
@@ -181,6 +182,7 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
private static final int MSG_CURRENT_USER_CHANGED = 4;
+ private static final int MSG_SYSTEM_READY = 5;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -351,6 +353,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages loading PointerIcons
private final PointerIconCache mPointerIconCache;
+ // Manages storage and retrieval of input data.
+ private final InputDataStore mInputDataStore;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -471,11 +476,9 @@ public class InputManagerService extends IInputManager.Stub
}
KeyboardBacklightControllerInterface getKeyboardBacklightController(
- NativeInputManagerService nativeService, PersistentDataStore dataStore) {
- return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
- ? new KeyboardBacklightController(mContext, nativeService, dataStore,
- mLooper, mUEventManager)
- : new KeyboardBacklightControllerInterface() {};
+ NativeInputManagerService nativeService) {
+ return new KeyboardBacklightController(mContext, nativeService, mLooper,
+ mUEventManager);
}
}
@@ -500,9 +503,11 @@ public class InputManagerService extends IInputManager.Stub
injector.getLooper(), this) : null;
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
injector.getUEventManager());
- mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
+ mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative);
mStickyModifierStateController = new StickyModifierStateController();
- mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
+ mInputDataStore = new InputDataStore();
+ mKeyGestureController = new KeyGestureController(mContext, injector.getLooper(),
+ mInputDataStore);
mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
mNative);
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -566,6 +571,14 @@ public class InputManagerService extends IInputManager.Stub
Watchdog.getInstance().addMonitor(this);
}
+ private void onBootPhase(int phase) {
+ // On ActivityManager thread, shift to handler to avoid blocking other system services in
+ // this boot phase.
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+ }
+ }
+
// TODO(BT) Pass in parameter for bluetooth system
public void systemRunning() {
if (DEBUG) {
@@ -648,6 +661,10 @@ public class InputManagerService extends IInputManager.Stub
mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
}
+ private void setDisplayTopologyInternal(DisplayTopology topology) {
+ mNative.setDisplayTopology(topology.getGraph());
+ }
+
/**
* Gets the current state of a key or button by key code.
* @param deviceId The input device id, or -1 to consult all devices.
@@ -3222,6 +3239,9 @@ public class InputManagerService extends IInputManager.Stub
case MSG_CURRENT_USER_CHANGED:
handleCurrentUserChanged((int) msg.obj);
break;
+ case MSG_SYSTEM_READY:
+ systemRunning();
+ break;
}
}
}
@@ -3426,10 +3446,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void onBootPhase(int phase) {
- // Called on ActivityManager thread.
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
- mService.systemRunning();
- }
+ mService.onBootPhase(phase);
}
@Override
@@ -3449,6 +3466,11 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopologyInternal(topology);
+ }
+
+ @Override
public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
boolean globallyInteractive = false;
ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 3b2305cec9db..bf08563db30d 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -81,6 +81,8 @@ class InputSettingsObserver extends ContentObserver {
(reason) -> updateTouchpadHardwareStateNotificationsEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
(reason) -> updateTouchpadRightClickZoneEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
+ (reason) -> updateTouchpadSystemGesturesEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -213,6 +215,10 @@ class InputSettingsObserver extends ContentObserver {
InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
}
+ private void updateTouchpadSystemGesturesEnabled() {
+ mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 55d2de2b6865..99c01ce5c15a 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -92,6 +92,8 @@ final class KeyGestureController {
| KeyEvent.META_SHIFT_ON;
private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
+ private static final int MSG_PERSIST_CUSTOM_GESTURES = 2;
+ private static final int MSG_LOAD_CUSTOM_GESTURES = 3;
// must match: config_settingsKeyBehavior in config.xml
private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0;
@@ -116,6 +118,8 @@ final class KeyGestureController {
private final SettingsObserver mSettingsObserver;
private final AppLaunchShortcutManager mAppLaunchShortcutManager;
private final InputGestureManager mInputGestureManager;
+ @GuardedBy("mInputDataStore")
+ private final InputDataStore mInputDataStore;
private static final Object mUserLock = new Object();
@UserIdInt
@GuardedBy("mUserLock")
@@ -155,7 +159,7 @@ final class KeyGestureController {
/** Currently fully consumed key codes per device */
private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
- KeyGestureController(Context context, Looper looper) {
+ KeyGestureController(Context context, Looper looper, InputDataStore inputDataStore) {
mContext = context;
mHandler = new Handler(looper, this::handleMessage);
mSystemPid = Process.myPid();
@@ -175,6 +179,7 @@ final class KeyGestureController {
mSettingsObserver = new SettingsObserver(mHandler);
mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
mInputGestureManager = new InputGestureManager(mContext);
+ mInputDataStore = inputDataStore;
initBehaviors();
initKeyCombinationRules();
}
@@ -434,6 +439,13 @@ final class KeyGestureController {
mSettingsObserver.observe();
mAppLaunchShortcutManager.systemRunning();
mInputGestureManager.systemRunning();
+
+ int userId;
+ synchronized (mUserLock) {
+ userId = mCurrentUserId;
+ }
+ // Load the system user's input gestures.
+ mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
}
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -955,6 +967,7 @@ final class KeyGestureController {
synchronized (mUserLock) {
mCurrentUserId = userId;
}
+ mHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget();
}
@MainThread
@@ -995,6 +1008,17 @@ final class KeyGestureController {
AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj;
notifyKeyGestureEvent(event);
break;
+ case MSG_PERSIST_CUSTOM_GESTURES: {
+ final int userId = (Integer) msg.obj;
+ persistInputGestures(userId);
+ break;
+ }
+ case MSG_LOAD_CUSTOM_GESTURES: {
+ final int userId = (Integer) msg.obj;
+ loadInputGestures(userId);
+ break;
+ }
+
}
return true;
}
@@ -1040,22 +1064,31 @@ final class KeyGestureController {
@InputManager.CustomInputGestureResult
public int addCustomInputGesture(@UserIdInt int userId,
@NonNull AidlInputGestureData inputGestureData) {
- return mInputGestureManager.addCustomInputGesture(userId,
+ final int result = mInputGestureManager.addCustomInputGesture(userId,
new InputGestureData(inputGestureData));
+ if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) {
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+ return result;
}
@BinderThread
@InputManager.CustomInputGestureResult
public int removeCustomInputGesture(@UserIdInt int userId,
@NonNull AidlInputGestureData inputGestureData) {
- return mInputGestureManager.removeCustomInputGesture(userId,
+ final int result = mInputGestureManager.removeCustomInputGesture(userId,
new InputGestureData(inputGestureData));
+ if (result == InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) {
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
+ }
+ return result;
}
@BinderThread
public void removeAllCustomInputGestures(@UserIdInt int userId,
@Nullable InputGestureData.Filter filter) {
mInputGestureManager.removeAllCustomInputGestures(userId, filter);
+ mHandler.obtainMessage(MSG_PERSIST_CUSTOM_GESTURES, userId).sendToTarget();
}
@BinderThread
@@ -1166,6 +1199,26 @@ final class KeyGestureController {
}
}
+ private void persistInputGestures(int userId) {
+ synchronized (mInputDataStore) {
+ final List<InputGestureData> inputGestureDataList =
+ mInputGestureManager.getCustomInputGestures(userId,
+ null);
+ mInputDataStore.saveInputGestures(userId, inputGestureDataList);
+ }
+ }
+
+ private void loadInputGestures(int userId) {
+ synchronized (mInputDataStore) {
+ mInputGestureManager.removeAllCustomInputGestures(userId, null);
+ final List<InputGestureData> inputGestureDataList = mInputDataStore.loadInputGestures(
+ userId);
+ for (final InputGestureData inputGestureData : inputGestureDataList) {
+ mInputGestureManager.addCustomInputGesture(userId, inputGestureData);
+ }
+ }
+ }
+
// A record of a registered key gesture event listener from one process.
private class KeyGestureHandlerRecord implements IBinder.DeathRecipient {
public final int mPid;
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 0defd27eaae2..16368c7678d1 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -33,6 +33,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UEventObserver;
+import android.sysprop.InputProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -47,7 +48,6 @@ import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;
import java.util.Objects;
-import java.util.OptionalInt;
import java.util.TreeSet;
/**
@@ -63,6 +63,10 @@ final class KeyboardBacklightController implements
// 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // To disable Framework controlled keyboard backlight animation run:
+ // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
+ private final boolean mKeyboardBacklightAnimationEnabled;
+
private enum Direction {
DIRECTION_UP, DIRECTION_DOWN
}
@@ -87,9 +91,6 @@ final class KeyboardBacklightController implements
private final Context mContext;
private final NativeInputManagerService mNative;
- // The PersistentDataStore should be locked before use.
- @GuardedBy("mDataStore")
- private final PersistentDataStore mDataStore;
private final Handler mHandler;
private final AnimatorFactory mAnimatorFactory;
private final UEventManager mUEventManager;
@@ -123,17 +124,15 @@ final class KeyboardBacklightController implements
}
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
- this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
+ Looper looper, UEventManager uEventManager) {
+ this(context, nativeService, looper, ValueAnimator::ofInt, uEventManager);
}
@VisibleForTesting
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
- UEventManager uEventManager) {
+ Looper looper, AnimatorFactory animatorFactory, UEventManager uEventManager) {
mContext = context;
mNative = nativeService;
- mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
mAnimatorFactory = animatorFactory;
mAmbientController = new AmbientKeyboardBacklightController(context, looper);
@@ -141,6 +140,8 @@ final class KeyboardBacklightController implements
Resources res = mContext.getResources();
mUserInactivityThresholdMs = res.getInteger(
com.android.internal.R.integer.config_keyboardBacklightTimeoutMs);
+ mKeyboardBacklightAnimationEnabled =
+ InputProperties.enable_keyboard_backlight_animation().orElse(false);
}
@Override
@@ -164,10 +165,8 @@ final class KeyboardBacklightController implements
}
}, UEVENT_KEYBOARD_BACKLIGHT_TAG);
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- // Start ambient backlight controller
- mAmbientController.systemRunning();
- }
+ // Start ambient backlight controller
+ mAmbientController.systemRunning();
}
@Override
@@ -229,9 +228,6 @@ final class KeyboardBacklightController implements
// level through keyboard up/down button
updateAmbientLightListener();
- maybeBackupBacklightBrightness(inputDevice, state.mLight,
- state.mBrightnessValueForLevel[newBrightnessLevel]);
-
if (DEBUG) {
Slog.d(TAG,
"Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
@@ -248,47 +244,6 @@ final class KeyboardBacklightController implements
}
}
- private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
- int brightnessValue) {
- // Don't back up or restore when ALS based keyboard backlight is enabled
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
- synchronized (mDataStore) {
- try {
- mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
- keyboardBacklight.getId(),
- brightnessValue);
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
- // Don't back up or restore when ALS based keyboard backlight is enabled
- if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
- KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
- OptionalInt brightness;
- synchronized (mDataStore) {
- brightness = mDataStore.getKeyboardBacklightBrightness(
- inputDevice.getDescriptor(), keyboardBacklight.getId());
- }
- if (state != null && brightness.isPresent()) {
- int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
- int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
- if (newLevel < 0) {
- newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
- }
- state.setBrightnessLevel(newLevel);
- if (DEBUG) {
- Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
- }
- }
- }
-
private void handleUserActivity() {
// Ignore user activity if device is not interactive. When device becomes interactive, we
// will send another user activity to turn backlight on.
@@ -393,7 +348,6 @@ final class KeyboardBacklightController implements
}
// The keyboard backlight was added or changed.
mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
- maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
}
private InputDevice getInputDevice(int deviceId) {
@@ -472,9 +426,6 @@ final class KeyboardBacklightController implements
}
private void updateAmbientLightListener() {
- if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
- return;
- }
boolean needToListenAmbientLightSensor = false;
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
@@ -555,8 +506,7 @@ final class KeyboardBacklightController implements
private int mBrightnessLevel;
private ValueAnimator mAnimator;
private final int[] mBrightnessValueForLevel;
- private boolean mUseAmbientController =
- InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
+ private boolean mUseAmbientController = true;
KeyboardBacklightState(int deviceId, Light light) {
mDeviceId = deviceId;
@@ -565,9 +515,6 @@ final class KeyboardBacklightController implements
}
private int[] setupBrightnessLevels() {
- if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) {
- return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
- }
int[] customLevels = mLight.getPreferredBrightnessLevels();
if (customLevels == null || customLevels.length == 0) {
return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
@@ -627,7 +574,7 @@ final class KeyboardBacklightController implements
if (fromValue == toValue) {
return;
}
- if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) {
+ if (mKeyboardBacklightAnimationEnabled) {
startAnimation(fromValue, toValue);
} else {
mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 728e44062e82..c72f7c076a83 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.input;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.input.InputSensorInfo;
import android.hardware.lights.Light;
@@ -42,6 +43,8 @@ interface NativeInputManagerService {
void setDisplayViewports(DisplayViewport[] viewports);
+ void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
int getScanCodeState(int deviceId, int sourceMask, int scanCode);
int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
@@ -147,6 +150,8 @@ interface NativeInputManagerService {
void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+ void setTouchpadSystemGesturesEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -321,6 +326,9 @@ interface NativeInputManagerService {
public native void setDisplayViewports(DisplayViewport[] viewports);
@Override
+ public native void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
+ @Override
public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
@Override
@@ -437,6 +445,9 @@ interface NativeInputManagerService {
public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
@Override
+ public native void setTouchpadSystemGesturesEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index a132876b72a3..0914b7e3eeb2 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -93,29 +93,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
mContext = context;
mPackageManagerInternal = packageManagerInternal;
mHandler = handler;
-
- IntentFilter integrityVerificationFilter = new IntentFilter();
- integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
- try {
- integrityVerificationFilter.addDataType(PACKAGE_MIME_TYPE);
- } catch (IntentFilter.MalformedMimeTypeException e) {
- throw new RuntimeException("Mime type malformed: should never happen.", e);
- }
-
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION.equals(
- intent.getAction())) {
- return;
- }
- mHandler.post(() -> handleIntegrityVerification(intent));
- }
- },
- integrityVerificationFilter,
- /* broadcastPermission= */ null,
- mHandler);
}
@Override
@@ -157,10 +134,4 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
public List<String> getWhitelistedRuleProviders() {
return Collections.emptyList();
}
-
- private void handleIntegrityVerification(Intent intent) {
- int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- }
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2e167efc4d81..6053557f575c 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -108,6 +108,7 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssConfiguration;
@@ -147,6 +148,7 @@ import com.android.server.location.provider.PassiveLocationProviderManager;
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -260,6 +262,11 @@ public class LocationManagerService extends ILocationManager.Stub implements
private volatile @Nullable GnssManagerService mGnssManagerService = null;
private ProxyGeocodeProvider mGeocodeProvider;
+ private @Nullable ProxyPopulationDensityProvider mPopulationDensityProvider = null;
+
+ // A cache for population density lookups. Used if density-based coarse locations are enabled.
+ private @Nullable LocationFudgerCache mLocationFudgerCache = null;
+
private final Object mDeprecatedGnssBatchingLock = new Object();
@GuardedBy("mDeprecatedGnssBatchingLock")
private @Nullable ILocationListener mDeprecatedGnssBatchingListener;
@@ -392,6 +399,25 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
+ @VisibleForTesting
+ protected void setProxyPopulationDensityProvider(ProxyPopulationDensityProvider provider) {
+ if (Flags.populationDensityProvider()) {
+ mPopulationDensityProvider = provider;
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudgerCache = cache;
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.setLocationFudgerCache(cache);
+ }
+ }
+
private void removeLocationProviderManager(LocationProviderManager manager) {
synchronized (mProviderManagers) {
boolean removed = mProviderManagers.remove(manager);
@@ -510,6 +536,17 @@ public class LocationManagerService extends ILocationManager.Stub implements
Log.e(TAG, "no geocoder provider found");
}
+ if (Flags.populationDensityProvider()) {
+ setProxyPopulationDensityProvider(
+ ProxyPopulationDensityProvider.createAndRegister(mContext));
+ if (mPopulationDensityProvider == null) {
+ Log.e(TAG, "no population density provider found");
+ }
+ }
+ if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) {
+ setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider));
+ }
+
// bind to hardware activity recognition
HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
HardwareActivityRecognitionProxy.createAndRegister(mContext);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
new file mode 100644
index 000000000000..4a533f42ffea
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -0,0 +1,246 @@
+/*
+ * 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.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubMessage;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that represents a broker for the endpoint registered by the client app. This class
+ * manages direct IContextHubEndpoint/IContextHubEndpointCallback API/callback calls.
+ *
+ * @hide
+ */
+public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ implements IBinder.DeathRecipient {
+ private static final String TAG = "ContextHubEndpointBroker";
+
+ /** The context of the service. */
+ private final Context mContext;
+
+ /** The proxy to talk to the Context Hub HAL. */
+ private final IContextHubWrapper mContextHubProxy;
+
+ /** The manager that registered this endpoint. */
+ private final ContextHubEndpointManager mEndpointManager;
+
+ /** Metadata about this endpoint (app-facing container). */
+ private final HubEndpointInfo mEndpointInfo;
+
+ /** Metadata about this endpoint (HAL-facing container). */
+ private final EndpointInfo mHalEndpointInfo;
+
+ /** The remote callback interface for this endpoint. */
+ private final IContextHubEndpointCallback mContextHubEndpointCallback;
+
+ /** True if this endpoint is registered with the service. */
+ private AtomicBoolean mIsRegistered = new AtomicBoolean(true);
+
+ private final Object mOpenSessionLock = new Object();
+
+ /** The set of session IDs that are pending remote acceptance */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mPendingSessionIds = new HashSet<>();
+
+ /** The set of session IDs that are actively enabled by this endpoint */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mActiveSessionIds = new HashSet<>();
+
+ /** The set of session IDs that are actively enabled by the remote endpoint */
+ @GuardedBy("mOpenSessionLock")
+ private final Set<Integer> mActiveRemoteSessionIds = new HashSet<>();
+
+ /* package */ ContextHubEndpointBroker(
+ Context context,
+ IContextHubWrapper contextHubProxy,
+ ContextHubEndpointManager endpointManager,
+ EndpointInfo halEndpointInfo,
+ IContextHubEndpointCallback callback) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mEndpointManager = endpointManager;
+ mEndpointInfo = new HubEndpointInfo(halEndpointInfo);
+ mHalEndpointInfo = halEndpointInfo;
+ mContextHubEndpointCallback = callback;
+ }
+
+ @Override
+ public HubEndpointInfo getAssignedHubEndpointInfo() {
+ return mEndpointInfo;
+ }
+
+ @Override
+ public int openSession(HubEndpointInfo destination, String serviceDescriptor)
+ throws RemoteException {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ int sessionId = mEndpointManager.reserveSessionId();
+ EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
+
+ synchronized (mOpenSessionLock) {
+ try {
+ mPendingSessionIds.add(sessionId);
+ mContextHubProxy.openEndpointSession(
+ sessionId,
+ halEndpointInfo.id,
+ mHalEndpointInfo.id,
+ serviceDescriptor);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling HAL openEndpointSession", e);
+ mPendingSessionIds.remove(sessionId);
+ mEndpointManager.returnSessionId(sessionId);
+ throw e;
+ }
+
+ return sessionId;
+ }
+ }
+
+ @Override
+ public void closeSession(int sessionId, int reason) throws RemoteException {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered");
+ try {
+ mContextHubProxy.closeEndpointSession(sessionId, (byte) reason);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
+ throw e;
+ }
+ }
+
+ @Override
+ public void unregister() {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ mIsRegistered.set(false);
+ try {
+ mContextHubProxy.unregisterEndpoint(mHalEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e);
+ }
+ synchronized (mOpenSessionLock) {
+ for (int id : mPendingSessionIds) {
+ mEndpointManager.returnSessionId(id);
+ }
+ for (int id : mActiveSessionIds) {
+ mEndpointManager.returnSessionId(id);
+ }
+ mPendingSessionIds.clear();
+ mActiveSessionIds.clear();
+ mActiveRemoteSessionIds.clear();
+ }
+ mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint());
+ }
+
+ @Override
+ public void openSessionRequestComplete(int sessionId) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+ synchronized (mOpenSessionLock) {
+ try {
+ mContextHubProxy.endpointSessionOpenComplete(sessionId);
+ mActiveRemoteSessionIds.add(sessionId);
+ } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+ Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
+ }
+ }
+ }
+
+ @Override
+ public void sendMessage(
+ int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
+ // TODO(b/381102453): Implement this
+ }
+
+ @Override
+ public void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode) {
+ // TODO(b/381102453): Implement this
+ }
+
+ /** Invoked when the underlying binder of this broker has died at the client process. */
+ @Override
+ public void binderDied() {
+ unregister();
+ }
+
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ if (mContextHubEndpointCallback != null) {
+ mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
+ }
+ }
+
+ /* package */ void onEndpointSessionOpenRequest(
+ int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionOpenRequest(
+ sessionId, initiator, serviceDescriptor);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e);
+ }
+ }
+ }
+
+ /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
+ synchronized (mOpenSessionLock) {
+ mPendingSessionIds.remove(sessionId);
+ mActiveSessionIds.remove(sessionId);
+ mActiveRemoteSessionIds.remove(sessionId);
+ }
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionClosed(sessionId, reason);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+ }
+ }
+ }
+
+ /* package */ void onEndpointSessionOpenComplete(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ mPendingSessionIds.remove(sessionId);
+ mActiveSessionIds.add(sessionId);
+ }
+ if (mContextHubEndpointCallback != null) {
+ try {
+ mContextHubEndpointCallback.onSessionOpenComplete(sessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onSessionClosed", e);
+ }
+ }
+ }
+
+ /* package */ boolean hasSessionId(int sessionId) {
+ synchronized (mOpenSessionLock) {
+ return mPendingSessionIds.contains(sessionId)
+ || mActiveSessionIds.contains(sessionId)
+ || mActiveRemoteSessionIds.contains(sessionId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
new file mode 100644
index 000000000000..8c5095f35f0d
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -0,0 +1,298 @@
+/*
+ * 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.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubEndpointManager
+ implements ContextHubHalEndpointCallback.IEndpointSessionCallback {
+ private static final String TAG = "ContextHubEndpointManager";
+
+ /** The hub ID of the Context Hub Service. */
+ private static final long SERVICE_HUB_ID = 0x416e64726f696400L;
+
+ /** The range of session IDs to use for endpoints */
+ private static final int SERVICE_SESSION_RANGE = 1024;
+
+ /** The length of the array that should be returned by HAL requestSessionIdRange */
+ private static final int SERVICE_SESSION_RANGE_LENGTH = 2;
+
+ /** The context of the service. */
+ private final Context mContext;
+
+ /** The proxy to talk to the Context Hub. */
+ private final IContextHubWrapper mContextHubProxy;
+
+ private final HubInfoRegistry mHubInfoRegistry;
+
+ /** A map of endpoint IDs to brokers currently registered. */
+ private final Map<Long, ContextHubEndpointBroker> mEndpointMap = new ConcurrentHashMap<>();
+
+ /** Variables for managing endpoint ID creation */
+ private final Object mEndpointLock = new Object();
+
+ @GuardedBy("mEndpointLock")
+ private long mNextEndpointId = 0;
+
+ /** The minimum session ID reservable by endpoints (retrieved from HAL) */
+ private final int mMinSessionId;
+
+ /** The minimum session ID reservable by endpoints (retrieved from HAL) */
+ private final int mMaxSessionId;
+
+ /** Variables for managing session ID creation */
+ private final Object mSessionIdLock = new Object();
+
+ /** A set of session IDs that have been reserved by an endpoint. */
+ @GuardedBy("mSessionIdLock")
+ private final Set<Integer> mReservedSessionIds =
+ Collections.newSetFromMap(new HashMap<Integer, Boolean>());
+
+ @GuardedBy("mSessionIdLock")
+ private int mNextSessionId = 0;
+
+ /** Initialized to true if all initialization in the constructor succeeds. */
+ private final boolean mSessionIdsValid;
+
+ /* package */ ContextHubEndpointManager(
+ Context context, IContextHubWrapper contextHubProxy, HubInfoRegistry hubInfoRegistry) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mHubInfoRegistry = hubInfoRegistry;
+ int[] range = null;
+ try {
+ range = mContextHubProxy.requestSessionIdRange(SERVICE_SESSION_RANGE);
+ if (range != null && range.length < SERVICE_SESSION_RANGE_LENGTH) {
+ Log.e(TAG, "Invalid session ID range: range array size = " + range.length);
+ range = null;
+ }
+ } catch (RemoteException | IllegalArgumentException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception while calling HAL requestSessionIdRange", e);
+ }
+
+ if (range == null) {
+ mMinSessionId = -1;
+ mMaxSessionId = -1;
+ mSessionIdsValid = false;
+ } else {
+ mMinSessionId = range[0];
+ mMaxSessionId = range[1];
+ if (!isSessionIdRangeValid(mMinSessionId, mMaxSessionId)) {
+ Log.e(
+ TAG,
+ "Invalid session ID range: max=" + mMaxSessionId + " min=" + mMinSessionId);
+ mSessionIdsValid = false;
+ } else {
+ mNextSessionId = mMinSessionId;
+ mSessionIdsValid = true;
+ }
+ }
+ }
+
+ /**
+ * Registers a new endpoint with the service.
+ *
+ * @param pendingEndpointInfo the object describing the endpoint being registered
+ * @param callback the callback interface of the endpoint to register
+ * @return the endpoint interface
+ * @throws IllegalStateException if max number of endpoints have already registered
+ */
+ /* package */ IContextHubEndpoint registerEndpoint(
+ HubEndpointInfo pendingEndpointInfo, IContextHubEndpointCallback callback)
+ throws RemoteException {
+ if (!mSessionIdsValid) {
+ throw new IllegalStateException("ContextHubEndpointManager failed to initialize");
+ }
+ ContextHubEndpointBroker broker;
+ long endpointId = getNewEndpointId();
+ EndpointInfo halEndpointInfo =
+ ContextHubServiceUtil.createHalEndpointInfo(
+ pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
+ try {
+ mContextHubProxy.registerEndpoint(halEndpointInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+ throw e;
+ }
+ broker =
+ new ContextHubEndpointBroker(
+ mContext,
+ mContextHubProxy,
+ this /* endpointManager */,
+ halEndpointInfo,
+ callback);
+ mEndpointMap.put(endpointId, broker);
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null
+ Log.e(TAG, "Failed to attach death recipient to client", e);
+ broker.unregister();
+ return null;
+ }
+
+ Log.d(TAG, "Registered endpoint with ID = " + endpointId);
+ return IContextHubEndpoint.Stub.asInterface(broker);
+ }
+
+ /**
+ * Reserves an available session ID for an endpoint.
+ *
+ * @throws IllegalStateException if no session ID was available.
+ * @return The reserved session ID.
+ */
+ /* package */ int reserveSessionId() {
+ int id = -1;
+ synchronized (mSessionIdLock) {
+ final int maxCapacity = mMaxSessionId - mMinSessionId + 1;
+ if (mReservedSessionIds.size() >= maxCapacity) {
+ throw new IllegalStateException("Too many sessions reserved");
+ }
+
+ id = mNextSessionId;
+ for (int i = mMinSessionId; i <= mMaxSessionId; i++) {
+ if (!mReservedSessionIds.contains(id)) {
+ mNextSessionId = (id == mMaxSessionId) ? mMinSessionId : id + 1;
+ break;
+ }
+
+ id = (id == mMaxSessionId) ? mMinSessionId : id + 1;
+ }
+
+ mReservedSessionIds.add(id);
+ }
+ return id;
+ }
+
+ /** Returns a previously reserved session ID through {@link #reserveSessionId()}. */
+ /* package */ void returnSessionId(int sessionId) {
+ synchronized (mSessionIdLock) {
+ mReservedSessionIds.remove(sessionId);
+ }
+ }
+
+ /**
+ * Unregisters an endpoint given its ID.
+ *
+ * @param endpointId The ID of the endpoint to unregister.
+ */
+ /* package */ void unregisterEndpoint(long endpointId) {
+ mEndpointMap.remove(endpointId);
+ }
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int sessionId,
+ HubEndpointInfo.HubEndpointIdentifier destination,
+ HubEndpointInfo.HubEndpointIdentifier initiator,
+ String serviceDescriptor) {
+ if (destination.getHub() != SERVICE_HUB_ID) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: invalid destination hub ID: "
+ + destination.getHub());
+ return;
+ }
+ ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
+ if (broker == null) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: unknown destination endpoint ID: "
+ + destination.getEndpoint());
+ return;
+ }
+ HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
+ if (initiatorInfo == null) {
+ Log.e(
+ TAG,
+ "onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
+ + initiator.getEndpoint());
+ return;
+ }
+ broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+ }
+
+ @Override
+ public void onCloseEndpointSession(int sessionId, byte reason) {
+ boolean callbackInvoked = false;
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ if (broker.hasSessionId(sessionId)) {
+ broker.onCloseEndpointSession(sessionId, reason);
+ callbackInvoked = true;
+ break;
+ }
+ }
+
+ if (!callbackInvoked) {
+ Log.w(TAG, "onCloseEndpointSession: unknown session ID " + sessionId);
+ }
+ }
+
+ @Override
+ public void onEndpointSessionOpenComplete(int sessionId) {
+ boolean callbackInvoked = false;
+ for (ContextHubEndpointBroker broker : mEndpointMap.values()) {
+ if (broker.hasSessionId(sessionId)) {
+ broker.onEndpointSessionOpenComplete(sessionId);
+ callbackInvoked = true;
+ break;
+ }
+ }
+
+ if (!callbackInvoked) {
+ Log.w(TAG, "onEndpointSessionOpenComplete: unknown session ID " + sessionId);
+ }
+ }
+
+ /** @return an available endpoint ID */
+ private long getNewEndpointId() {
+ synchronized (mEndpointLock) {
+ if (mNextEndpointId == Long.MAX_VALUE) {
+ throw new IllegalStateException("Too many endpoints connected");
+ }
+ return mNextEndpointId++;
+ }
+ }
+
+ /**
+ * @return true if the provided session ID range is valid
+ */
+ private boolean isSessionIdRangeValid(int minId, int maxId) {
+ return (minId <= maxId) && (minId >= 0) && (maxId >= 0);
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
index c05f7a0c0e00..9d52c6a020f4 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -26,6 +26,7 @@ import android.os.RemoteException;
public class ContextHubHalEndpointCallback
extends android.hardware.contexthub.IEndpointCallback.Stub {
private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+ private final IEndpointSessionCallback mEndpointSessionCallback;
/** Interface for listening for endpoint start and stop events. */
public interface IEndpointLifecycleCallback {
@@ -36,8 +37,27 @@ public class ContextHubHalEndpointCallback
void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
}
- ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) {
+ /** Interface for listening for endpoint session events. */
+ public interface IEndpointSessionCallback {
+ /** Called when an endpoint session open is requested by the HAL. */
+ void onEndpointSessionOpenRequest(
+ int sessionId,
+ HubEndpointInfo.HubEndpointIdentifier destinationId,
+ HubEndpointInfo.HubEndpointIdentifier initiatorId,
+ String serviceDescriptor);
+
+ /** Called when a endpoint close session is completed. */
+ void onCloseEndpointSession(int sessionId, byte reason);
+
+ /** Called when a requested endpoint open session is completed */
+ void onEndpointSessionOpenComplete(int sessionId);
+ }
+
+ ContextHubHalEndpointCallback(
+ IEndpointLifecycleCallback endpointLifecycleCallback,
+ IEndpointSessionCallback endpointSessionCallback) {
mEndpointLifecycleCallback = endpointLifecycleCallback;
+ mEndpointSessionCallback = endpointSessionCallback;
}
@Override
@@ -48,7 +68,7 @@ public class ContextHubHalEndpointCallback
}
HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
for (int i = 0; i < halEndpointInfos.length; i++) {
- endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]);
+ endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
}
mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
}
@@ -72,14 +92,23 @@ public class ContextHubHalEndpointCallback
@Override
public void onEndpointSessionOpenRequest(
- int i, EndpointId endpointId, EndpointId endpointId1, String s)
- throws RemoteException {}
+ int i, EndpointId destination, EndpointId initiator, String s) throws RemoteException {
+ HubEndpointInfo.HubEndpointIdentifier destinationId =
+ new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id);
+ HubEndpointInfo.HubEndpointIdentifier initiatorId =
+ new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id);
+ mEndpointSessionCallback.onEndpointSessionOpenRequest(i, destinationId, initiatorId, s);
+ }
@Override
- public void onCloseEndpointSession(int i, byte b) throws RemoteException {}
+ public void onCloseEndpointSession(int i, byte b) throws RemoteException {
+ mEndpointSessionCallback.onCloseEndpointSession(i, b);
+ }
@Override
- public void onEndpointSessionOpenComplete(int i) throws RemoteException {}
+ public void onEndpointSessionOpenComplete(int i) throws RemoteException {
+ mEndpointSessionCallback.onEndpointSessionOpenComplete(i);
+ }
@Override
public int getInterfaceVersion() throws RemoteException {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index d177d0e5f2eb..d916eda693d8 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -155,6 +155,9 @@ public class ContextHubService extends IContextHubService.Stub {
// The manager for sending messages to/from clients
private ContextHubClientManager mClientManager;
+ // Manages endpoint and its sessions between apps and HAL
+ private ContextHubEndpointManager mEndpointManager;
+
// The default client for old API clients
private Map<Integer, IContextHubClient> mDefaultClientMap;
@@ -330,14 +333,18 @@ public class ContextHubService extends IContextHubService.Stub {
HubInfoRegistry registry;
try {
registry = new HubInfoRegistry(mContextHubWrapper);
+ mEndpointManager =
+ new ContextHubEndpointManager(mContext, mContextHubWrapper, registry);
Log.i(TAG, "Enabling generic offload API");
} catch (UnsupportedOperationException e) {
+ mEndpointManager = null;
registry = null;
Log.w(TAG, "Generic offload API not supported, disabling");
}
mHubInfoRegistry = registry;
} else {
mHubInfoRegistry = null;
+ mEndpointManager = null;
Log.i(TAG, "Disabling generic offload API");
}
@@ -527,7 +534,7 @@ public class ContextHubService extends IContextHubService.Stub {
}
try {
mContextHubWrapper.registerEndpointCallback(
- new ContextHubHalEndpointCallback(mHubInfoRegistry));
+ new ContextHubHalEndpointCallback(mHubInfoRegistry, mEndpointManager));
} catch (RemoteException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while registering IEndpointCallback", e);
}
@@ -790,8 +797,11 @@ public class ContextHubService extends IContextHubService.Stub {
HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
throws RemoteException {
super.registerEndpoint_enforcePermission();
- // TODO(b/375487784): Implement this
- return null;
+ if (mEndpointManager == null) {
+ Log.e(TAG, "Endpoint manager failed to initialize");
+ throw new UnsupportedOperationException("Endpoint registration is not supported");
+ }
+ return mEndpointManager.registerEndpoint(pendingHubEndpointInfo, callback);
}
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@@ -799,7 +809,8 @@ public class ContextHubService extends IContextHubService.Stub {
public void registerEndpointDiscoveryCallbackId(
long endpointId, IContextHubEndpointDiscoveryCallback callback) throws RemoteException {
super.registerEndpointDiscoveryCallbackId_enforcePermission();
- // TODO(b/375487784): Implement this
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.registerEndpointDiscoveryCallback(endpointId, callback);
}
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@@ -808,7 +819,8 @@ public class ContextHubService extends IContextHubService.Stub {
String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback)
throws RemoteException {
super.registerEndpointDiscoveryCallbackDescriptor_enforcePermission();
- // TODO(b/375487784): Implement this
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.registerEndpointDiscoveryCallback(serviceDescriptor, callback);
}
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@@ -816,7 +828,15 @@ public class ContextHubService extends IContextHubService.Stub {
public void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback)
throws RemoteException {
super.unregisterEndpointDiscoveryCallback_enforcePermission();
- // TODO(b/375487784): Implement this
+ checkEndpointDiscoveryPreconditions();
+ mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback);
+ }
+
+ private void checkEndpointDiscoveryPreconditions() {
+ if (mHubInfoRegistry == null) {
+ Log.e(TAG, "Hub endpoint registry failed to initialize");
+ throw new UnsupportedOperationException("Endpoint discovery is not supported");
+ }
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 33d2ff02986a..05be427f9daf 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -18,6 +18,9 @@ package com.android.server.location.contexthub;
import android.Manifest;
import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.HostEndPoint;
@@ -415,4 +418,51 @@ import java.util.List;
static String formatDateFromTimestamp(long timeStampInMs) {
return DATE_FORMATTER.format(Instant.ofEpochMilli(timeStampInMs));
}
+
+ /**
+ * Converts a context hub HAL EndpointInfo object based on the provided HubEndpointInfo.
+ *
+ * @param info the HubEndpointInfo object
+ * @return the equivalent EndpointInfo object
+ */
+ /* package */
+ static EndpointInfo convertHalEndpointInfo(HubEndpointInfo info) {
+ return createHalEndpointInfo(
+ info, info.getIdentifier().getEndpoint(), info.getIdentifier().getHub());
+ }
+
+ /**
+ * Creates a context hub HAL EndpointInfo object based on the provided HubEndpointInfo. As
+ * opposed to convertHalEndpointInfo, this method can be used to overwrite/specify the endpoint
+ * and hub ID.
+ *
+ * @param info the HubEndpointInfo object
+ * @param endpointId the endpoint ID of this object
+ * @param hubId the hub ID of this object
+ * @return the equivalent EndpointInfo object
+ */
+ /* package */
+ static EndpointInfo createHalEndpointInfo(HubEndpointInfo info, long endpointId, long hubId) {
+ EndpointInfo outputInfo = new EndpointInfo();
+ outputInfo.id = new android.hardware.contexthub.EndpointId();
+ outputInfo.id.id = endpointId;
+ outputInfo.id.hubId = hubId;
+ outputInfo.name = info.getName();
+ outputInfo.version = info.getVersion();
+ outputInfo.tag = info.getTag();
+ Collection<String> permissions = info.getRequiredPermissions();
+ outputInfo.requiredPermissions = permissions.toArray(new String[permissions.size()]);
+ Collection<HubServiceInfo> services = info.getServiceInfoCollection();
+ outputInfo.services = new android.hardware.contexthub.Service[services.size()];
+ int i = 0;
+ for (HubServiceInfo service : services) {
+ outputInfo.services[i] = new android.hardware.contexthub.Service();
+ outputInfo.services[i].format = service.getFormat();
+ outputInfo.services[i].serviceDescriptor = service.getServiceDescriptor();
+ outputInfo.services[i].majorVersion = service.getMajorVersion();
+ outputInfo.services[i].minorVersion = service.getMinorVersion();
+ i++;
+ }
+ return outputInfo;
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index da31bf29a8e8..ccfa61b400b6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -492,14 +492,21 @@ import java.util.concurrent.atomic.AtomicInteger;
/* package */
void onTransactionResponse(int transactionId, boolean success) {
TransactionAcceptConditions conditions =
- transaction -> transaction.getTransactionId() == transactionId;
+ transaction -> {
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(
+ TAG,
+ "Unexpected transaction: expected "
+ + transactionId
+ + ", received "
+ + transaction.getTransactionId());
+ return false;
+ }
+ return true;
+ };
ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (expected ID = "
- + transactionId
- + ", received ID = "
- + transaction.getTransactionId()
- + ")");
+ Log.w(TAG, "Received unexpected transaction response");
return;
}
@@ -581,7 +588,7 @@ import java.util.concurrent.atomic.AtomicInteger;
transaction.getTransactionType() == ContextHubTransaction.TYPE_QUERY_NANOAPPS;
ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ Log.w(TAG, "Received unexpected query response");
return;
}
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index d2b2331d54f3..6f5f191849e2 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -18,7 +18,9 @@ package com.android.server.location.contexthub;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubServiceInfo;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.location.HubInfo;
+import android.os.DeadObjectException;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -29,6 +31,9 @@ import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiConsumer;
class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
private static final String TAG = "HubInfoRegistry";
@@ -43,6 +48,56 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo>
mHubEndpointInfos = new ArrayMap<>();
+ /**
+ * A wrapper class that is used to store arguments to
+ * ContextHubManager.registerEndpointCallback.
+ */
+ private static class DiscoveryCallback {
+ private final IContextHubEndpointDiscoveryCallback mCallback;
+ private final Optional<Long> mEndpointId;
+ private final Optional<String> mServiceDescriptor;
+
+ DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) {
+ mCallback = callback;
+ mEndpointId = Optional.of(endpointId);
+ mServiceDescriptor = Optional.empty();
+ }
+
+ DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) {
+ mCallback = callback;
+ mEndpointId = Optional.empty();
+ mServiceDescriptor = Optional.of(serviceDescriptor);
+ }
+
+ public IContextHubEndpointDiscoveryCallback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * @param info The hub endpoint info to check
+ * @return true if info matches
+ */
+ public boolean isMatch(HubEndpointInfo info) {
+ if (mEndpointId.isPresent()) {
+ return mEndpointId.get() == info.getIdentifier().getEndpoint();
+ }
+ if (mServiceDescriptor.isPresent()) {
+ for (HubServiceInfo serviceInfo : info.getServiceInfoCollection()) {
+ if (mServiceDescriptor.get().equals(serviceInfo.getServiceDescriptor())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /* The list of discovery callbacks registered with the service */
+ @GuardedBy("mCallbackLock")
+ private final List<DiscoveryCallback> mEndpointDiscoveryCallbacks = new ArrayList<>();
+
+ private final Object mCallbackLock = new Object();
+
HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
mContextHubWrapper = contextHubWrapper;
refreshCachedHubs();
@@ -87,6 +142,12 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
}
}
+ public HubEndpointInfo getEndpointInfo(HubEndpointInfo.HubEndpointIdentifier id) {
+ synchronized (mLock) {
+ return mHubEndpointInfos.get(id);
+ }
+ }
+
/** Invoked when HAL restarts */
public void onHalRestart() {
synchronized (mLock) {
@@ -103,16 +164,50 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
}
}
+
+ invokeForMatchingEndpoints(
+ endpointInfos,
+ (cb, infoList) -> {
+ try {
+ cb.onEndpointsStarted(infoList);
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "onEndpointStarted: callback died, unregistering");
+ unregisterEndpointDiscoveryCallback(cb);
+ } else {
+ Log.e(TAG, "Exception while calling onEndpointsStarted", e);
+ }
+ }
+ });
}
@Override
public void onEndpointStopped(
HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) {
+ ArrayList<HubEndpointInfo> removedInfoList = new ArrayList<>();
synchronized (mLock) {
for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) {
- mHubEndpointInfos.remove(endpointId);
+ HubEndpointInfo info = mHubEndpointInfos.remove(endpointId);
+ if (info != null) {
+ removedInfoList.add(info);
+ }
}
}
+
+ invokeForMatchingEndpoints(
+ removedInfoList.toArray(new HubEndpointInfo[removedInfoList.size()]),
+ (cb, infoList) -> {
+ try {
+ cb.onEndpointsStopped(infoList, reason);
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ Log.w(TAG, "onEndpointStopped: callback died, unregistering");
+ unregisterEndpointDiscoveryCallback(cb);
+ } else {
+ Log.e(TAG, "Exception while calling onEndpointsStopped", e);
+ }
+ }
+ });
}
/** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */
@@ -145,6 +240,77 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl
return searchResult;
}
+ /* package */
+ void registerEndpointDiscoveryCallback(
+ long endpointId, IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ checkCallbackAlreadyRegistered(callback);
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId));
+ }
+ }
+
+ /* package */
+ void registerEndpointDiscoveryCallback(
+ String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ checkCallbackAlreadyRegistered(callback);
+ mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor));
+ }
+ }
+
+ /* package */
+ void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ if (discoveryCallback.getCallback().asBinder() == callback.asBinder()) {
+ mEndpointDiscoveryCallbacks.remove(discoveryCallback);
+ break;
+ }
+ }
+ }
+ }
+
+ private void checkCallbackAlreadyRegistered(
+ IContextHubEndpointDiscoveryCallback callback) {
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ if (discoveryCallback.mCallback.asBinder() == callback.asBinder()) {
+ throw new IllegalArgumentException("Callback is already registered");
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterates through all registered discovery callbacks and invokes a given callback for those
+ * that match the endpoints the callback is targeted for.
+ *
+ * @param endpointInfos The list of endpoint infos to check for a match.
+ * @param consumer The callback to invoke, which consumes the callback object and the list of
+ * matched endpoint infos.
+ */
+ private void invokeForMatchingEndpoints(
+ HubEndpointInfo[] endpointInfos,
+ BiConsumer<IContextHubEndpointDiscoveryCallback, HubEndpointInfo[]> consumer) {
+ synchronized (mCallbackLock) {
+ for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+ ArrayList<HubEndpointInfo> infoList = new ArrayList<>();
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ if (discoveryCallback.isMatch(endpointInfo)) {
+ infoList.add(endpointInfo);
+ }
+ }
+
+ consumer.accept(
+ discoveryCallback.getCallback(),
+ infoList.toArray(new HubEndpointInfo[infoList.size()]));
+ }
+ }
+ }
+
void dump(IndentingPrintWriter ipw) {
synchronized (mLock) {
dumpLocked(ipw);
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 9b729eb11eed..6cb942980403 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -18,6 +18,7 @@ package com.android.server.location.contexthub;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.chre.flags.Flags;
+import android.hardware.contexthub.EndpointId;
import android.hardware.contexthub.HostEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.MessageDeliveryStatus;
@@ -239,6 +240,30 @@ public abstract class IContextHubWrapper {
public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
throws RemoteException {}
+ /** Registers the endpoint with the ContextHub HAL */
+ public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {}
+
+ /** Unregisters a previously registered endpoint */
+ public int[] requestSessionIdRange(int size) throws RemoteException {
+ return null;
+ }
+
+ /** Opens an endpoint session between two endpoints */
+ public void openEndpointSession(
+ int sessionId, EndpointId destination, EndpointId initiator, String serviceDescriptor)
+ throws RemoteException {}
+
+ /** Closes a previously opened endpoint */
+ public void closeEndpointSession(int sessionId, byte reason) throws RemoteException {}
+
+ /** Unregisters a previously registered endpoint */
+ public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {}
+
+ /** Notifies the completion of a session opened by the HAL */
+ public void endpointSessionOpenComplete(int sessionId) throws RemoteException {}
+
/**
* @return True if this version of the Contexthub HAL supports Location setting notifications.
*/
@@ -671,6 +696,67 @@ public abstract class IContextHubWrapper {
hub.registerEndpointCallback(cb);
}
+ @Override
+ public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.registerEndpoint(info);
+ }
+
+ @Override
+ public int[] requestSessionIdRange(int size) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return null;
+ }
+ return hub.requestSessionIdRange(size);
+ }
+
+ @Override
+ public void openEndpointSession(
+ int sessionId,
+ EndpointId destination,
+ EndpointId initiator,
+ String serviceDescriptor)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.openEndpointSession(sessionId, destination, initiator, serviceDescriptor);
+ }
+
+ @Override
+ public void closeEndpointSession(int sessionId, byte reason) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.closeEndpointSession(sessionId, reason);
+ }
+
+ @Override
+ public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.unregisterEndpoint(info);
+ }
+
+ @Override
+ public void endpointSessionOpenComplete(int sessionId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+ hub.endpointSessionOpenComplete(sessionId);
+ }
+
public boolean supportsLocationSettingNotifications() {
return true;
}
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 88a269706470..0da1514872d6 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,13 +16,16 @@
package com.android.server.location.fudger;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.geometry.S2CellIdUtils;
import java.security.SecureRandom;
import java.time.Clock;
@@ -83,6 +86,9 @@ public class LocationFudger {
@GuardedBy("this")
@Nullable private LocationResult mCachedCoarseLocationResult;
+ @GuardedBy("this")
+ @Nullable private LocationFudgerCache mLocationFudgerCache = null;
+
public LocationFudger(float accuracyM) {
this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
}
@@ -97,6 +103,16 @@ public class LocationFudger {
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ synchronized (this) {
+ mLocationFudgerCache = cache;
+ }
+ }
+
+ /**
* Resets the random offsets completely.
*/
public void resetOffsets() {
@@ -162,16 +178,34 @@ public class LocationFudger {
longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
- // quantize location by snapping to a grid. this is the primary means of obfuscation. it
- // gives nice consistent results and is very effective at hiding the true location (as long
- // as you are not sitting on a grid boundary, which the random offsets mitigate).
- //
- // note that we quantize the latitude first, since the longitude quantization depends on the
- // latitude value and so leaks information about the latitude
- double latGranularity = metersToDegreesLatitude(mAccuracyM);
- latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
- double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
- longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ // We copy a reference to the cache, so even if mLocationFudgerCache is concurrently set
+ // to null, we can continue executing the condition below.
+ LocationFudgerCache cacheCopy = null;
+ synchronized (this) {
+ cacheCopy = mLocationFudgerCache;
+ }
+
+ // TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
+ // new density-based algorithm, while the second is the traditional coarsening algorithm.
+ // Once rollout is done, clean up the unused algorithm.
+ if (Flags.densityBasedCoarseLocations() && cacheCopy != null
+ && cacheCopy.hasDefaultValue()) {
+ int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+ double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
+ latitude = center[S2CellIdUtils.LAT_INDEX];
+ longitude = center[S2CellIdUtils.LNG_INDEX];
+ } else {
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as
+ // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+ //
+ // note that we quantize the latitude first, since the longitude quantization depends on
+ // the latitude value and so leaks information about the latitude
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ }
coarse.setLatitude(latitude);
coarse.setLongitude(longitude);
@@ -185,6 +219,15 @@ public class LocationFudger {
return coarse;
}
+ @VisibleForTesting
+ protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
+ long coarsenedCell = S2CellIdUtils.getParent(leafCell, level);
+ double[] center = new double[] {0.0, 0.0};
+ S2CellIdUtils.toLatLngDegrees(coarsenedCell, center);
+ return center;
+ }
+
/**
* Update the random offsets over time.
*
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
new file mode 100644
index 000000000000..ce8bec8f0147
--- /dev/null
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -0,0 +1,200 @@
+/*
+ * 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.location.fudger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import java.util.Objects;
+
+/**
+ * A cache for returning the coarsening level to be used. The coarsening level depends on the user
+ * location. If the cache contains the requested latitude/longitude, the s2 level of the cached
+ * cell id is returned. If not, a default value is returned.
+ * This class has a {@link ProxyPopulationDensityProvider} used to refresh the cache.
+ * This cache exists because {@link ProxyPopulationDensityProvider} must be queried asynchronously,
+ * whereas a synchronous answer is needed.
+ * The cache is first-in, first-out, and has a fixed size. Cache entries are valid until evicted by
+ * another value.
+ */
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public class LocationFudgerCache {
+
+ // The maximum number of S2 cell ids stored in the cache.
+ // Each cell id is a long, so the memory requirement is 8*MAX_CACHE_SIZE bytes.
+ protected static final int MAX_CACHE_SIZE = 20;
+
+ private final Object mLock = new Object();
+
+ // mCache is a circular buffer of size MAX_CACHE_SIZE. The next position to be written to is
+ // mPosInCache. Initially, the cache is filled with INVALID_CELL_IDs.
+ @GuardedBy("mLock")
+ private final long[] mCache = new long[MAX_CACHE_SIZE];
+
+ @GuardedBy("mLock")
+ private int mPosInCache = 0;
+
+ @GuardedBy("mLock")
+ private int mCacheSize = 0;
+
+ // The S2 level to coarsen to, if the cache doesn't contain a better answer.
+ // Updated concurrently by callbacks.
+ @GuardedBy("mLock")
+ private Integer mDefaultCoarseningLevel = null;
+
+ // The provider that asynchronously provides what is stored in the cache.
+ private final ProxyPopulationDensityProvider mPopulationDensityProvider;
+
+ private static String sTAG = "LocationFudgerCache";
+
+ public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) {
+ mPopulationDensityProvider = Objects.requireNonNull(provider);
+
+ asyncFetchDefaultCoarseningLevel();
+ }
+
+ /** Returns true if the cache has successfully received a default value from the provider. */
+ public boolean hasDefaultValue() {
+ synchronized (mLock) {
+ return (mDefaultCoarseningLevel != null);
+ }
+ }
+
+ /**
+ * Returns the S2 level to which the provided location should be coarsened.
+ * The answer comes from the cache if available, otherwise the default value is returned.
+ */
+ public int getCoarseningLevel(double latitudeDegrees, double longitudeDegrees) {
+ // If we still haven't received the default level from the provider, try fetching it again.
+ // The answer wouldn't come in time, but it will be used for the following queries.
+ if (!hasDefaultValue()) {
+ asyncFetchDefaultCoarseningLevel();
+ }
+ Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees);
+ if (s2CellId == null) {
+ // Asynchronously queries the density from the provider. The answer won't come in time,
+ // but it will update the cache for the following queries.
+ refreshCache(latitudeDegrees, longitudeDegrees);
+
+ return getDefaultCoarseningLevel();
+ }
+ return S2CellIdUtils.getLevel(s2CellId);
+ }
+
+ /**
+ * If the cache contains the current location, returns the corresponding S2 cell id.
+ * Otherwise, returns null.
+ */
+ @Nullable
+ private Long readCacheForLatLng(double latDegrees, double lngDegrees) {
+ synchronized (mLock) {
+ for (int i = 0; i < mCacheSize; i++) {
+ if (S2CellIdUtils.containsLatLngDegrees(mCache[i], latDegrees, lngDegrees)) {
+ return mCache[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Adds the provided s2 cell id to the cache. This might evict other values from the cache. */
+ public void addToCache(long s2CellId) {
+ addToCache(new long[] {s2CellId});
+ }
+
+ /**
+ * Adds the provided s2 cell ids to the cache. This might evict other values from the cache.
+ * If more than MAX_CACHE_SIZE elements are provided, only the first elements are copied.
+ * The first element of the input is added last into the FIFO cache, so it gets evicted last.
+ */
+ public void addToCache(long[] s2CellIds) {
+ synchronized (mLock) {
+ // Only copy up to MAX_CACHE_SIZE elements
+ int end = Math.min(s2CellIds.length, MAX_CACHE_SIZE);
+ mCacheSize = Math.min(mCacheSize + end, MAX_CACHE_SIZE);
+
+ // Add in reverse so the first cell of s2CellIds is the last evicted
+ for (int i = end - 1; i >= 0; i--) {
+ mCache[mPosInCache] = s2CellIds[i];
+ mPosInCache = (mPosInCache + 1) % MAX_CACHE_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Queries the population density provider for the default coarsening level (to be used if the
+ * cache doesn't contain a better answer), and updates mDefaultCoarseningLevel with the answer.
+ */
+ private void asyncFetchDefaultCoarseningLevel() {
+ IS2LevelCallback callback = new IS2LevelCallback.Stub() {
+ @Override
+ public void onResult(int s2level) {
+ synchronized (mLock) {
+ mDefaultCoarseningLevel = Integer.valueOf(s2level);
+ }
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get default population density");
+ }
+ };
+ mPopulationDensityProvider.getDefaultCoarseningLevel(callback);
+ }
+
+ /**
+ * Queries the population density provider and store the result in the cache.
+ */
+ private void refreshCache(double latitude, double longitude) {
+ IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() {
+ @Override
+ public void onResult(long[] s2CellIds) {
+ addToCache(s2CellIds);
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get population density");
+ }
+ };
+ mPopulationDensityProvider.getCoarsenedS2Cells(latitude, longitude, MAX_CACHE_SIZE - 1,
+ callback);
+ }
+
+ /**
+ * Returns the default S2 level to coarsen to. This should be used if the cache
+ * does not provide a better answer.
+ */
+ private int getDefaultCoarseningLevel() {
+ synchronized (mLock) {
+ // The minimum valid level is 0.
+ if (mDefaultCoarseningLevel == null) {
+ return 0;
+ }
+ return mDefaultCoarseningLevel;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4a9bf88aae33..a8c90100ebf0 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -48,6 +48,7 @@ import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -105,6 +106,7 @@ import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
@@ -1663,6 +1665,18 @@ public class LocationProviderManager extends
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudger.setLocationFudgerCache(cache);
+ }
+
+ /**
* Returns true if this provider is visible to the current caller (whether called from a binder
* thread or not). If a provider isn't visible, then all APIs return the same data they would if
* the provider didn't exist (i.e. the caller can't see or use the provider).
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
new file mode 100644
index 000000000000..7b454e481dda
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
@@ -0,0 +1,120 @@
+/*
+ * 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.location.provider.proxy;
+
+import static android.location.provider.PopulationDensityProviderBase.ACTION_POPULATION_DENSITY_PROVIDER;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.IPopulationDensityProvider;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IPopulationDensityProvider implementations.
+ */
+public class ProxyPopulationDensityProvider {
+
+ public static final String TAG = "ProxyPopulationDensityProvider";
+
+ final ServiceWatcher mServiceWatcher;
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyPopulationDensityProvider createAndRegister(Context context) {
+ ProxyPopulationDensityProvider proxy = new ProxyPopulationDensityProvider(context);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private ProxyPopulationDensityProvider(Context context) {
+ mServiceWatcher = ServiceWatcher.create(
+ context,
+ "PopulationDensityProxy",
+ CurrentUserServiceSupplier.createFromConfig(
+ context,
+ ACTION_POPULATION_DENSITY_PROVIDER,
+ com.android.internal.R.bool.config_enablePopulationDensityProviderOverlay,
+ com.android.internal.R.string.config_populationDensityProviderPackageName),
+ null);
+ }
+
+ private boolean register() {
+ boolean resolves = mServiceWatcher.checkServiceResolves();
+ if (resolves) {
+ mServiceWatcher.register();
+ }
+ return resolves;
+ }
+
+ /** Gets the default coarsening level. */
+ public void getDefaultCoarseningLevel(IS2LevelCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getDefaultCoarseningLevel(callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying default coarsening level");
+ }
+ }
+ });
+ }
+
+
+ /** Gets the population density at the requested location. */
+ public void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+ int numAdditionalCells, IS2CellIdsCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getCoarsenedS2Cells(latitudeDegrees, longitudeDegrees,
+ numAdditionalCells, callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying coarsened S2 cell");
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0f65d1d44789..2686f2b30d27 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -46,6 +46,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -378,7 +379,12 @@ import java.util.Objects;
Slog.e(
TAG,
"Could not map this selected device attribute type to an available route: "
- + selectedDeviceAttributesType);
+ + selectedDeviceAttributesType
+ + ". Available types: "
+ + Arrays.toString(
+ Arrays.stream(audioDeviceInfos)
+ .map(AudioDeviceInfo::getType)
+ .toArray()));
// We know mRouteIdToAvailableDeviceRoutes is not empty.
newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e886f3b9f54e..abc067d4aa9c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -246,7 +246,7 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
if (hasSystemRoutingPermissions) {
MediaRoute2ProviderInfo providerInfo =
- userRecord.mHandler.mSystemProvider.getProviderInfo();
+ userRecord.mHandler.getSystemProvider().getProviderInfo();
if (providerInfo != null) {
systemRoutes = providerInfo.getRoutes();
} else {
@@ -258,7 +258,8 @@ class MediaRouter2ServiceImpl {
}
} else {
systemRoutes = new ArrayList<>();
- systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
+ systemRoutes.add(
+ userRecord.mHandler.getSystemProvider().getDefaultRoute());
}
}
return new ArrayList<>(systemRoutes);
@@ -850,10 +851,10 @@ class MediaRouter2ServiceImpl {
if (setDeviceRouteSelected) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
- return userRecord.mHandler.mSystemProvider
+ return userRecord.mHandler.getSystemProvider()
.generateDeviceRouteSelectedSessionInfo(targetPackageName);
} else {
- sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+ sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
if (!sessionInfos.isEmpty()) {
// Return a copy of the current system session with no modification,
// except setting the client package name.
@@ -866,7 +867,7 @@ class MediaRouter2ServiceImpl {
}
} else {
return new RoutingSessionInfo.Builder(
- userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
+ userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
.setClientPackageName(targetPackageName)
.build();
}
@@ -1136,6 +1137,7 @@ class MediaRouter2ServiceImpl {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
RouterRecord routerRecord =
new RouterRecord(
+ mContext,
userRecord,
router,
uid,
@@ -1374,7 +1376,7 @@ class MediaRouter2ServiceImpl {
}
manager.mLastSessionCreationRequest = null;
} else {
- String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -1462,7 +1464,7 @@ class MediaRouter2ServiceImpl {
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
UserHandler userHandler = routerRecord.mUserRecord.mHandler;
- String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+ String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -2055,6 +2057,7 @@ class MediaRouter2ServiceImpl {
}
final class RouterRecord implements IBinder.DeathRecipient {
+ public final Context mContext;
public final UserRecord mUserRecord;
public final String mPackageName;
public final List<Integer> mSelectRouteSequenceNumbers;
@@ -2073,6 +2076,7 @@ class MediaRouter2ServiceImpl {
@Nullable public RouteListingPreference mRouteListingPreference;
RouterRecord(
+ Context context,
UserRecord userRecord,
IMediaRouter2 router,
int uid,
@@ -2082,6 +2086,7 @@ class MediaRouter2ServiceImpl {
boolean hasModifyAudioRoutingPermission,
boolean hasMediaContentControlPermission,
boolean hasMediaRoutingControl) {
+ mContext = context;
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
@@ -2125,11 +2130,11 @@ class MediaRouter2ServiceImpl {
notifyRoutesUpdated(routesToReport.values().stream().toList());
List<RoutingSessionInfo> sessionInfos =
- mUserRecord.mHandler.mSystemProvider.getSessionInfos();
+ mUserRecord.mHandler.getSystemProvider().getSessionInfos();
RoutingSessionInfo systemSessionToReport =
newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
? sessionInfos.get(0)
- : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
+ : mUserRecord.mHandler.getSystemProvider().getDefaultSessionInfo();
notifySessionInfoChanged(systemSessionToReport);
}
}
@@ -2279,7 +2284,7 @@ class MediaRouter2ServiceImpl {
if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system
// route info from them.
- route = mUserRecord.mHandler.mSystemProvider.getDefaultRoute();
+ route = mUserRecord.mHandler.getSystemProvider().getDefaultRoute();
}
mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
} catch (RemoteException ex) {
@@ -2322,18 +2327,45 @@ class MediaRouter2ServiceImpl {
}
/**
- * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
- * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
+ * Returns a filtered copy of {@code routes} that contains only the routes that are visible
+ * to this RouterRecord.
*/
private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
for (MediaRoute2Info route : routes) {
- if (route.isVisibleTo(mPackageName)) {
+ if (route.isVisibleTo(mPackageName) && hasPermissionsToSeeRoute(route)) {
filteredRoutes.add(route);
}
}
return filteredRoutes;
}
+
+ /**
+ * @return whether this RouterRecord has the required permissions to see the given route.
+ */
+ private boolean hasPermissionsToSeeRoute(MediaRoute2Info route) {
+ if (!Flags.enableRouteVisibilityControlApi()) {
+ return true;
+ }
+ List<Set<String>> permissionSets = route.getRequiredPermissions();
+ if (permissionSets.isEmpty()) {
+ return true;
+ }
+ for (Set<String> permissionSet : permissionSets) {
+ boolean hasAllInSet = true;
+ for (String permission : permissionSet) {
+ if (mContext.checkPermission(permission, mPid, mUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ hasAllInSet = false;
+ break;
+ }
+ }
+ if (hasAllInSet) {
+ return true;
+ }
+ }
+ return false;
+ }
}
final class ManagerRecord implements IBinder.DeathRecipient {
@@ -2535,6 +2567,10 @@ class MediaRouter2ServiceImpl {
private boolean mRunning;
+ private SystemMediaRoute2Provider getSystemProvider() {
+ return mSystemProvider;
+ }
+
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler(
@NonNull MediaRouter2ServiceImpl service,
@@ -2549,19 +2585,19 @@ class MediaRouter2ServiceImpl {
service.mContext, UserHandle.of(userRecord.mUserId), looper)
: new SystemMediaRoute2Provider(
service.mContext, UserHandle.of(userRecord.mUserId), looper);
- mRouteProviders.add(mSystemProvider);
+ mRouteProviders.add(getSystemProvider());
mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
this, mUserRecord.mUserId);
}
void init() {
- mSystemProvider.setCallback(this);
+ getSystemProvider().setCallback(this);
}
private void start() {
if (!mRunning) {
mRunning = true;
- mSystemProvider.start();
+ getSystemProvider().start();
mWatcher.start();
}
}
@@ -2570,7 +2606,7 @@ class MediaRouter2ServiceImpl {
if (mRunning) {
mRunning = false;
mWatcher.stop(); // also stops all providers
- mSystemProvider.stop();
+ getSystemProvider().stop();
}
}
@@ -2662,7 +2698,7 @@ class MediaRouter2ServiceImpl {
String indent = prefix + " ";
pw.println(indent + "mRunning=" + mRunning);
- mSystemProvider.dump(pw, prefix);
+ getSystemProvider().dump(pw, prefix);
mWatcher.dump(pw, prefix);
}
@@ -2755,7 +2791,7 @@ class MediaRouter2ServiceImpl {
hasAddedOrModifiedRoutes,
hasRemovedRoutes,
provider.mIsSystemRouteProvider,
- mSystemProvider.getDefaultRoute());
+ getSystemProvider().getDefaultRoute());
}
private static String getPackageNameFromNullableRecord(
@@ -2969,7 +3005,8 @@ class MediaRouter2ServiceImpl {
}
// Bypass checking router if it's the system session (routerRecord should be null)
- if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
+ if (TextUtils.equals(
+ getProviderId(uniqueSessionId), getSystemProvider().getUniqueId())) {
return true;
}
@@ -3100,7 +3137,7 @@ class MediaRouter2ServiceImpl {
&& !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system routing
// session info from them.
- sessionInfo = mSystemProvider.getDefaultSessionInfo();
+ sessionInfo = getSystemProvider().getDefaultSessionInfo();
}
matchingRequest.mRouterRecord.notifySessionCreated(
toOriginalRequestId(uniqueRequestId), sessionInfo);
@@ -3114,13 +3151,13 @@ class MediaRouter2ServiceImpl {
}
// For system provider, notify all routers.
- if (provider == mSystemProvider) {
+ if (provider == getSystemProvider()) {
if (mServiceRef.get() == null) {
return;
}
notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
notifySessionInfoChangedToRouters(
- getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
+ getRouterRecords(false), getSystemProvider().getDefaultSessionInfo());
return;
}
@@ -3256,7 +3293,8 @@ class MediaRouter2ServiceImpl {
MediaRoute2ProviderInfo systemProviderInfo = null;
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
// TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
- if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
+ if (TextUtils.equals(
+ providerInfo.getUniqueId(), getSystemProvider().getUniqueId())) {
// Adding routes from system provider will be handled below, so skip it here.
systemProviderInfo = providerInfo;
continue;
@@ -3272,10 +3310,10 @@ class MediaRouter2ServiceImpl {
// This shouldn't happen.
Slog.wtf(TAG, "System route provider not found.");
}
- currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
+ currentSystemSessionInfo = getSystemProvider().getSessionInfos().get(0);
} else {
- currentRoutes.add(mSystemProvider.getDefaultRoute());
- currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
+ currentRoutes.add(getSystemProvider().getDefaultRoute());
+ currentSystemSessionInfo = getSystemProvider().getDefaultSessionInfo();
}
if (!currentRoutes.isEmpty()) {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index c8a87994ee16..e7b79abed73a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
@@ -98,11 +97,8 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
- public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
- // For an app to be eligible for FGS delegation, it needs a media session liked to a media
- // notification. Currently, notifications cannot be linked to MediaSession2 so it is not
- // supported.
- return null;
+ public boolean hasLinkedNotificationSupport() {
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 668ee2adbd9f..5f7c86f7b670 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,7 +29,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
@@ -184,8 +183,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private final UriGrantsManagerInternal mUgmInternal;
private final Context mContext;
- private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
-
private final Object mLock = new Object();
// This field is partially guarded by mLock. Writes and non-atomic iterations (for example:
// index-based-iterations) must be guarded by mLock. But it is safe to acquire an iterator
@@ -306,32 +303,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mPolicies = policies;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
- mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
-
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
- private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
- return new ForegroundServiceDelegationOptions.Builder()
- .setClientPid(mOwnerPid)
- .setClientUid(getUid())
- .setClientPackageName(getPackageName())
- .setClientAppThread(null)
- .setSticky(false)
- .setClientInstanceName(
- "MediaSessionFgsDelegate_"
- + getUid()
- + "_"
- + mOwnerPid
- + "_"
- + getPackageName())
- .setForegroundServiceTypes(0)
- .setDelegationService(
- ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
- .build();
- }
-
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -389,6 +364,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return mUserId;
}
+ @Override
+ public boolean hasLinkedNotificationSupport() {
+ return true;
+ }
+
/**
* Check if this session has system priorty and should receive media buttons
* before any other sessions.
@@ -752,11 +732,6 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return mPackageName + "/" + mTag + "/" + getUniqueId() + " (userId=" + mUserId + ")";
}
- @Override
- public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
- return mForegroundServiceDelegationOptions;
- }
-
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 6c3b1234935a..396660816b45 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,10 +16,8 @@
package com.android.server.media;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.Notification;
import android.media.AudioManager;
-import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -63,13 +61,14 @@ public abstract class MediaSessionRecordImpl {
public abstract int getUserId();
/**
- * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
- * service with changes in the {@link PlaybackState} for this session.
+ * Returns whether this session supports linked notifications.
*
- * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
- * manager service with changes in the {@link PlaybackState} for this session.
+ * <p>A notification is linked to a media session if it contains
+ * {@link android.app.Notification#EXTRA_MEDIA_SESSION}.
+ *
+ * @return {@code true} if this session supports linked notifications, {@code false} otherwise.
*/
- public abstract ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+ public abstract boolean hasLinkedNotificationSupport();
/**
* Check if this session has system priority and should receive media buttons before any other
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2b29fbd9c5b5..e091fc62451b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,7 +30,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -185,9 +184,9 @@ public class MediaSessionService extends SystemService implements Monitor {
/**
* Maps uid with all user engaged session records associated to it. It's used for calling
- * ActivityManagerInternal startFGS and stopFGS. This collection doesn't contain
- * MediaSession2Record(s). When the media session is paused, There exists a timeout before
- * calling stopFGS unlike usage logging which considers it disengaged immediately.
+ * ActivityManagerInternal internal api to set fgs active/inactive. This collection doesn't
+ * contain MediaSession2Record(s). When the media session is paused, There exists a timeout
+ * before setting FGS inactive unlike usage logging which considers it disengaged immediately.
*/
@GuardedBy("mLock")
private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs =
@@ -195,7 +194,7 @@ public class MediaSessionService extends SystemService implements Monitor {
/* Maps uid with all media notifications associated to it */
@GuardedBy("mLock")
- private final Map<Integer, Set<Notification>> mMediaNotifications = new HashMap<>();
+ private final Map<Integer, Set<StatusBarNotification>> mMediaNotifications = new HashMap<>();
/**
* Holds all {@link MediaSessionRecordImpl} which we've reported as being {@link
@@ -700,10 +699,10 @@ public class MediaSessionService extends SystemService implements Monitor {
MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
if (isUserEngaged) {
addUserEngagedSession(mediaSessionRecord);
- startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
+ setFgsActiveIfSessionIsLinkedToNotification(mediaSessionRecord);
} else {
removeUserEngagedSession(mediaSessionRecord);
- stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
+ setFgsInactiveIfNoSessionIsLinkedToNotification(mediaSessionRecord);
}
}
@@ -737,17 +736,20 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- private void startFgsIfSessionIsLinkedToNotification(
+ private void setFgsActiveIfSessionIsLinkedToNotification(
MediaSessionRecordImpl mediaSessionRecord) {
- Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ Log.d(TAG, "setFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
return;
}
+ if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+ return;
+ }
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- startFgsDelegateLocked(mediaSessionRecord);
+ for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (mediaSessionRecord.isLinkedToNotification(sbn.getNotification())) {
+ setFgsActiveLocked(mediaSessionRecord, sbn);
return;
}
}
@@ -755,81 +757,92 @@ public class MediaSessionService extends SystemService implements Monitor {
}
@GuardedBy("mLock")
- private void startFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return; // This record doesn't support FGS. Typically a MediaSession2 record.
- }
+ private void setFgsActiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+ StatusBarNotification sbn) {
if (!mFgsAllowedMediaSessionRecords.add(mediaSessionRecord)) {
- return; // This record is already FGS-started.
+ return; // This record already is FGS-activated.
}
final long token = Binder.clearCallingIdentity();
try {
+ final String packageName = sbn.getPackageName();
+ final int uid = sbn.getUid();
+ final int notificationId = sbn.getId();
Log.i(
TAG,
TextUtils.formatSimple(
- "startFgsDelegate: pkg=%s uid=%d",
- foregroundServiceDelegationOptions.mClientPackageName,
- foregroundServiceDelegationOptions.mClientUid));
- mActivityManagerInternal.startForegroundServiceDelegate(
- foregroundServiceDelegationOptions, /* connection= */ null);
+ "setFgsActiveLocked: pkg=%s uid=%d notification=%d",
+ packageName, uid, notificationId));
+ mActivityManagerInternal.notifyActiveMediaForegroundService(packageName,
+ sbn.getUser().getIdentifier(), notificationId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
- private void stopFgsIfNoSessionIsLinkedToNotification(
+ @Nullable
+ private StatusBarNotification getLinkedNotification(
+ int uid, MediaSessionRecordImpl record) {
+ synchronized (mLock) {
+ for (StatusBarNotification sbn :
+ mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (record.isLinkedToNotification(sbn.getNotification())) {
+ return sbn;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void setFgsInactiveIfNoSessionIsLinkedToNotification(
MediaSessionRecordImpl mediaSessionRecord) {
- Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ Log.d(TAG, "setFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
return;
}
+ if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+ return;
+ }
synchronized (mLock) {
- int uid = mediaSessionRecord.getUid();
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return;
- }
-
+ final int uid = mediaSessionRecord.getUid();
for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification :
+ for (StatusBarNotification sbn :
mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (record.isLinkedToNotification(mediaNotification)) {
+ if (record.isLinkedToNotification(sbn.getNotification())) {
// A user engaged session linked with a media notification is found.
// We shouldn't call stop FGS in this case.
return;
}
}
}
-
- stopFgsDelegateLocked(mediaSessionRecord);
+ final StatusBarNotification linkedNotification =
+ getLinkedNotification(uid, mediaSessionRecord);
+ if (linkedNotification != null) {
+ setFgsInactiveLocked(mediaSessionRecord, linkedNotification);
+ }
}
}
@GuardedBy("mLock")
- private void stopFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return; // This record doesn't support FGS. Typically a MediaSession2 record.
- }
+ private void setFgsInactiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+ StatusBarNotification sbn) {
if (!mFgsAllowedMediaSessionRecords.remove(mediaSessionRecord)) {
- return; // This record is not FGS-started. No need to stop it.
+ return; // This record is not FGS-active. No need to set inactive.
}
final long token = Binder.clearCallingIdentity();
try {
+ final String packageName = sbn.getPackageName();
+ final int userId = sbn.getUser().getIdentifier();
+ final int uid = sbn.getUid();
+ final int notificationId = sbn.getId();
Log.i(
TAG,
TextUtils.formatSimple(
- "stopFgsDelegate: pkg=%s uid=%d",
- foregroundServiceDelegationOptions.mClientPackageName,
- foregroundServiceDelegationOptions.mClientUid));
- mActivityManagerInternal.stopForegroundServiceDelegate(
- foregroundServiceDelegationOptions);
+ "setFgsInactiveLocked: pkg=%s uid=%d notification=%d",
+ packageName, uid, notificationId));
+ mActivityManagerInternal.notifyInactiveMediaForegroundService(packageName,
+ userId, notificationId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3259,18 +3272,18 @@ public class MediaSessionService extends SystemService implements Monitor {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
- Notification postedNotification = sbn.getNotification();
int uid = sbn.getUid();
+ final Notification postedNotification = sbn.getNotification();
if (!postedNotification.isMediaNotification()) {
return;
}
synchronized (mLock) {
mMediaNotifications.putIfAbsent(uid, new HashSet<>());
- mMediaNotifications.get(uid).add(postedNotification);
+ mMediaNotifications.get(uid).add(sbn);
for (MediaSessionRecordImpl mediaSessionRecord :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
if (mediaSessionRecord.isLinkedToNotification(postedNotification)) {
- startFgsDelegateLocked(mediaSessionRecord);
+ setFgsActiveLocked(mediaSessionRecord, sbn);
return;
}
}
@@ -3286,9 +3299,9 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- Set<Notification> uidMediaNotifications = mMediaNotifications.get(uid);
+ Set<StatusBarNotification> uidMediaNotifications = mMediaNotifications.get(uid);
if (uidMediaNotifications != null) {
- uidMediaNotifications.remove(removedNotification);
+ uidMediaNotifications.remove(sbn);
if (uidMediaNotifications.isEmpty()) {
mMediaNotifications.remove(uid);
}
@@ -3300,8 +3313,7 @@ public class MediaSessionService extends SystemService implements Monitor {
if (notificationRecord == null) {
return;
}
-
- stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
+ setFgsInactiveIfNoSessionIsLinkedToNotification(notificationRecord);
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 49897b9209cc..5fb80ba92091 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -90,7 +90,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private volatile SessionCreationOrTransferRequest mPendingTransferRequest;
SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
- super(COMPONENT_NAME, /* isSystemRouteProvider= */ true);
+ this(context, COMPONENT_NAME, user, looper);
+ }
+
+ protected SystemMediaRoute2Provider(
+ Context context, ComponentName componentName, UserHandle user, Looper looper) {
+ super(componentName, /* isSystemRouteProvider= */ true);
mContext = context;
mUser = user;
mHandler = new Handler(looper);
@@ -588,7 +593,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@Override
protected String getDebugString() {
return TextUtils.formatSimple(
- "SystemMR2Provider - package: %s, selected route id: %s, bluetooth impl: %s",
+ "%s - package: %s, selected route id: %s, bluetooth impl: %s",
+ getClass().getSimpleName(),
mComponentName.getPackageName(),
mSelectedRouteId,
mBluetoothRouteController.getClass().getSimpleName());
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index a86e81881212..8a14a3866860 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.content.ComponentName;
import android.content.Context;
import android.media.MediaRoute2ProviderService;
import android.os.Looper;
@@ -28,7 +29,13 @@ import android.os.UserHandle;
* <p>System routes are those which can handle the system audio and/or video.
*/
/* package */ class SystemMediaRoute2Provider2 extends SystemMediaRoute2Provider {
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(
+ SystemMediaRoute2Provider2.class.getPackage().getName(),
+ SystemMediaRoute2Provider2.class.getName());
+
SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
- super(context, user, looper);
+ super(context, COMPONENT_NAME, user, looper);
}
}
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 c7e00d3cbb24..1673b8e6a0af 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -25,16 +25,21 @@ import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
-import android.media.quality.MediaQualityContract;
+import android.media.quality.MediaQualityContract.BaseParameters;
import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
import android.media.quality.PictureProfileHandle;
import android.media.quality.SoundProfile;
+import android.media.quality.SoundProfileHandle;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.util.Log;
import com.android.server.SystemService;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
import org.json.JSONException;
import org.json.JSONObject;
@@ -42,6 +47,8 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.UUID;
+import java.util.stream.Collectors;
/**
* This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -53,10 +60,14 @@ public class MediaQualityService extends SystemService {
private static final String TAG = "MediaQualityService";
private final Context mContext;
private final MediaQualityDbHelper mMediaQualityDbHelper;
+ private final BiMap<Long, String> mPictureProfileTempIdMap;
+ private final BiMap<Long, String> mSoundProfileTempIdMap;
public MediaQualityService(Context context) {
super(context);
mContext = context;
+ mPictureProfileTempIdMap = HashBiMap.create();
+ mSoundProfileTempIdMap = HashBiMap.create();
mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
mMediaQualityDbHelper.setIdleConnectionTimeout(30);
@@ -71,47 +82,53 @@ public class MediaQualityService extends SystemService {
private final class BinderService extends IMediaQualityManager.Stub {
@Override
- public PictureProfile createPictureProfile(PictureProfile pp) {
+ public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
- values.put(MediaQualityContract.BaseParameters.PARAMETER_TYPE, pp.getProfileType());
- values.put(MediaQualityContract.BaseParameters.PARAMETER_NAME, pp.getName());
- values.put(MediaQualityContract.BaseParameters.PARAMETER_PACKAGE, pp.getPackageName());
- values.put(MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID, pp.getInputId());
- values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters()));
+ values.put(BaseParameters.PARAMETER_TYPE, pp.getProfileType());
+ values.put(BaseParameters.PARAMETER_NAME, pp.getName());
+ values.put(BaseParameters.PARAMETER_PACKAGE, pp.getPackageName());
+ values.put(BaseParameters.PARAMETER_INPUT_ID, pp.getInputId());
+ values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(pp.getParameters()));
// id is auto-generated by SQLite upon successful insertion of row
- long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, null, values);
- return new PictureProfile.Builder(pp).setProfileId(Long.toString(id)).build();
+ Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ null, values);
+ populateTempIdMap(mPictureProfileTempIdMap, id);
+ pp.setProfileId(mPictureProfileTempIdMap.get(id));
+ return pp;
}
@Override
- public void updatePictureProfile(String id, PictureProfile pp) {
+ public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
// TODO: implement
}
+
@Override
- public void removePictureProfile(String id) {
- // TODO: implement
+ public void removePictureProfile(String id, UserHandle user) {
+ Long intId = mPictureProfileTempIdMap.inverse().get(id);
+ if (intId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(intId)};
+ db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
+ selectionArgs);
+ mPictureProfileTempIdMap.remove(intId);
+ }
}
@Override
- public PictureProfile getPictureProfile(int type, String name) {
- SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
-
- String selection = MediaQualityContract.BaseParameters.PARAMETER_TYPE + " = ? AND "
- + MediaQualityContract.BaseParameters.PARAMETER_NAME + " = ?";
+ public PictureProfile getPictureProfile(int type, String name, boolean includeParams,
+ UserHandle user) {
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_NAME + " = ?";
String[] selectionArguments = {Integer.toString(type), name};
try (
- Cursor cursor = db.query(
+ Cursor cursor = getCursorAfterQuerying(
mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- getAllPictureProfileColumns(),
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)
+ getAllMediaProfileColumns(), selection, selectionArguments)
) {
int count = cursor.getCount();
if (count == 0) {
@@ -120,171 +137,320 @@ public class MediaQualityService extends SystemService {
if (count > 1) {
Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
+ " in %s. Should only ever be 0 or 1.", count, type, name,
- mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
return null;
}
cursor.moveToFirst();
- return getPictureProfileFromCursor(cursor);
+ return getPictureProfileWithTempIdFromCursor(cursor);
}
}
- private String bundleToJson(PersistableBundle bundle) {
- JSONObject jsonObject = new JSONObject();
- if (bundle == null) {
- return jsonObject.toString();
+ @Override
+ public List<PictureProfile> getPictureProfilesByPackage(
+ String packageName, boolean includeParams, UserHandle user) {
+ String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {packageName};
+ return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
+ selectionArguments);
+ }
+
+ @Override
+ public List<PictureProfile> getAvailablePictureProfiles(
+ boolean includeParams, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
+ // TODO: pass the profile ID to MediaQuality HAL when ready.
+ return false;
+ }
+
+ @Override
+ public List<String> getPictureProfilePackageNames(UserHandle user) {
+ String [] column = {BaseParameters.PARAMETER_PACKAGE};
+ List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
+ null, null);
+ return pictureProfiles.stream()
+ .map(PictureProfile::getPackageName)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(BaseParameters.PARAMETER_TYPE, sp.getProfileType());
+ values.put(BaseParameters.PARAMETER_NAME, sp.getName());
+ values.put(BaseParameters.PARAMETER_PACKAGE, sp.getPackageName());
+ values.put(BaseParameters.PARAMETER_INPUT_ID, sp.getInputId());
+ values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(sp.getParameters()));
+
+ // id is auto-generated by SQLite upon successful insertion of row
+ Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ null, values);
+ populateTempIdMap(mSoundProfileTempIdMap, id);
+ sp.setProfileId(mSoundProfileTempIdMap.get(id));
+ return sp;
+ }
+
+ @Override
+ public void updateSoundProfile(String id, SoundProfile pp, UserHandle user) {
+ // TODO: implement
+ }
+
+ @Override
+ public void removeSoundProfile(String id, UserHandle user) {
+ Long intId = mSoundProfileTempIdMap.inverse().get(id);
+ if (intId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(intId)};
+ db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
+ selectionArgs);
+ mSoundProfileTempIdMap.remove(intId);
}
- for (String key : bundle.keySet()) {
- try {
- jsonObject.put(key, bundle.getString(key));
- } catch (JSONException e) {
- Log.e(TAG, "Unable to serialize ", e);
+ }
+
+ @Override
+ public SoundProfile getSoundProfile(int type, String id, boolean includeParams,
+ UserHandle user) {
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArguments = {String.valueOf(type), id};
+
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ getAllMediaProfileColumns(), selection, selectionArguments)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s"
+ + " in %s. Should only ever be 0 or 1.", count, id,
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
+ return null;
}
+ cursor.moveToFirst();
+ return getSoundProfileWithTempIdFromCursor(cursor);
}
- return jsonObject.toString();
}
- private PersistableBundle jsonToBundle(String jsonString) {
- JSONObject jsonObject = null;
- PersistableBundle bundle = new PersistableBundle();
+ @Override
+ public List<SoundProfile> getSoundProfilesByPackage(
+ String packageName, boolean includeParams, UserHandle user) {
+ String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {packageName};
+ return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
+ selectionArguments);
+ }
+
+ @Override
+ public List<SoundProfile> getAvailableSoundProfiles(
+ boolean includeParams, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
+ // TODO: pass the profile ID to MediaQuality HAL when ready.
+ return false;
+ }
- try {
- jsonObject = new JSONObject(jsonString);
+ @Override
+ public List<String> getSoundProfilePackageNames(UserHandle user) {
+ String [] column = {BaseParameters.PARAMETER_NAME};
+ List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
+ null, null);
+ return soundProfiles.stream()
+ .map(SoundProfile::getPackageName)
+ .distinct()
+ .collect(Collectors.toList());
+ }
- Iterator<String> keys = jsonObject.keys();
- while (keys.hasNext()) {
- String key = keys.next();
- Object value = jsonObject.get(key);
+ private void populateTempIdMap(BiMap<Long, String> map, Long id) {
+ if (id != null && map.get(id) == null) {
+ String uuid = UUID.randomUUID().toString();
+ while (map.inverse().containsKey(uuid)) {
+ uuid = UUID.randomUUID().toString();
+ }
+ map.put(id, uuid);
+ }
+ }
+ private String persistableBundleToJson(PersistableBundle bundle) {
+ JSONObject json = new JSONObject();
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ try {
if (value instanceof String) {
- bundle.putString(key, (String) value);
+ json.put(key, bundle.getString(key));
} else if (value instanceof Integer) {
- bundle.putInt(key, (Integer) value);
+ json.put(key, bundle.getInt(key));
+ } else if (value instanceof Long) {
+ json.put(key, bundle.getLong(key));
} else if (value instanceof Boolean) {
- bundle.putBoolean(key, (Boolean) value);
+ json.put(key, bundle.getBoolean(key));
} else if (value instanceof Double) {
- bundle.putDouble(key, (Double) value);
- } else if (value instanceof Long) {
- bundle.putLong(key, (Long) value);
+ json.put(key, bundle.getDouble(key));
}
+ } catch (JSONException e) {
+ Log.e(TAG, "Unable to serialize ", e);
}
- } catch (JSONException e) {
- throw new RuntimeException(e);
}
+ return json.toString();
+ }
+ private PersistableBundle jsonToBundle(String jsonString) {
+ PersistableBundle bundle = new PersistableBundle();
+ if (jsonString != null) {
+ JSONObject jsonObject = null;
+ try {
+ jsonObject = new JSONObject(jsonString);
+
+ Iterator<String> keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = jsonObject.get(key);
+
+ if (value instanceof String) {
+ bundle.putString(key, (String) value);
+ } else if (value instanceof Integer) {
+ bundle.putInt(key, (Integer) value);
+ } else if (value instanceof Boolean) {
+ bundle.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Double) {
+ bundle.putDouble(key, (Double) value);
+ } else if (value instanceof Long) {
+ bundle.putLong(key, (Long) value);
+ }
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
return bundle;
}
- private String[] getAllPictureProfileColumns() {
+ private String[] getAllMediaProfileColumns() {
return new String[]{
- MediaQualityContract.BaseParameters.PARAMETER_ID,
- MediaQualityContract.BaseParameters.PARAMETER_TYPE,
- MediaQualityContract.BaseParameters.PARAMETER_NAME,
- MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID,
- MediaQualityContract.BaseParameters.PARAMETER_PACKAGE,
+ BaseParameters.PARAMETER_ID,
+ BaseParameters.PARAMETER_TYPE,
+ BaseParameters.PARAMETER_NAME,
+ BaseParameters.PARAMETER_INPUT_ID,
+ BaseParameters.PARAMETER_PACKAGE,
mMediaQualityDbHelper.SETTINGS
};
}
- private PictureProfile getPictureProfileFromCursor(Cursor cursor) {
- String returnId = cursor.getString(cursor.getColumnIndexOrThrow(
- MediaQualityContract.BaseParameters.PARAMETER_ID));
- int type = cursor.getInt(cursor.getColumnIndexOrThrow(
- MediaQualityContract.BaseParameters.PARAMETER_TYPE));
- String name = cursor.getString(cursor.getColumnIndexOrThrow(
- MediaQualityContract.BaseParameters.PARAMETER_NAME));
- String inputId = cursor.getString(cursor.getColumnIndexOrThrow(
- MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID));
- String packageName = cursor.getString(cursor.getColumnIndexOrThrow(
- MediaQualityContract.BaseParameters.PARAMETER_PACKAGE));
- String settings = cursor.getString(
- cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS));
- return new PictureProfile(returnId, type, name, inputId,
- packageName, jsonToBundle(settings));
+ private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
+ return new PictureProfile(
+ getTempId(mPictureProfileTempIdMap, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToBundle(getSettingsString(cursor)),
+ PictureProfileHandle.NONE
+ );
}
- @Override
- public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
- String selection = MediaQualityContract.BaseParameters.PARAMETER_PACKAGE + " = ?";
- String[] selectionArguments = {packageName};
- return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection,
- selectionArguments);
+ private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) {
+ return new SoundProfile(
+ getTempId(mSoundProfileTempIdMap, cursor),
+ getType(cursor),
+ getName(cursor),
+ getInputId(cursor),
+ getPackageName(cursor),
+ jsonToBundle(getSettingsString(cursor)),
+ SoundProfileHandle.NONE
+ );
}
- @Override
- public List<PictureProfile> getAvailablePictureProfiles() {
- return new ArrayList<>();
+ private String getTempId(BiMap<Long, String> map, Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
+ Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
+ populateTempIdMap(map, dbId);
+ return map.get(dbId);
}
- @Override
- public List<String> getPictureProfilePackageNames() {
- String [] column = {MediaQualityContract.BaseParameters.PARAMETER_NAME};
- List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
- null, null);
- List<String> packageNames = new ArrayList<>();
- for (PictureProfile pictureProfile: pictureProfiles) {
- packageNames.add(pictureProfile.getName());
- }
- return packageNames;
+ private int getType(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
+ return colIndex != -1 ? cursor.getInt(colIndex) : 0;
}
- private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
- String selection, String[] selectionArguments) {
+ private String getName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getInputId(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getPackageName(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private String getSettingsString(Cursor cursor) {
+ int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS);
+ return colIndex != -1 ? cursor.getString(colIndex) : null;
+ }
+
+ private Cursor getCursorAfterQuerying(String table, String[] columns, String selection,
+ String[] selectionArgs) {
SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+ return db.query(table, columns, selection, selectionArgs,
+ /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null);
+ }
+ private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
+ String selection, String[] selectionArguments) {
try (
- Cursor cursor = db.query(
- mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- columns,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)
) {
List<PictureProfile> pictureProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
- pictureProfiles.add(getPictureProfileFromCursor(cursor));
+ pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor));
}
return pictureProfiles;
}
}
- @Override
- public PictureProfileHandle getPictureProfileHandle(String id) {
- return null;
- }
-
- @Override
- public SoundProfile createSoundProfile(SoundProfile pp) {
- // TODO: implement
- return pp;
- }
- @Override
- public void updateSoundProfile(String id, SoundProfile pp) {
- // TODO: implement
- }
- @Override
- public void removeSoundProfile(String id) {
- // TODO: implement
- }
- @Override
- public SoundProfile getSoundProfile(int type, String id) {
- return null;
- }
- @Override
- public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
- return new ArrayList<>();
- }
- @Override
- public List<SoundProfile> getAvailableSoundProfiles() {
- return new ArrayList<>();
- }
- @Override
- public List<String> getSoundProfilePackageNames() {
- return new ArrayList<>();
+ private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns,
+ String selection, String[] selectionArguments) {
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)
+ ) {
+ List<SoundProfile> soundProfiles = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor));
+ }
+ return soundProfiles;
+ }
}
-
@Override
public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
}
@@ -297,70 +463,71 @@ public class MediaQualityService extends SystemService {
}
@Override
- public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+ public void setAmbientBacklightSettings(
+ AmbientBacklightSettings settings, UserHandle user) {
}
@Override
- public void setAmbientBacklightEnabled(boolean enabled) {
+ public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
}
@Override
- public List<ParamCapability> getParamCapabilities(List<String> names) {
+ public List<ParamCapability> getParamCapabilities(List<String> names, UserHandle user) {
return new ArrayList<>();
}
@Override
- public List<String> getPictureProfileAllowList() {
+ public List<String> getPictureProfileAllowList(UserHandle user) {
return new ArrayList<>();
}
@Override
- public void setPictureProfileAllowList(List<String> packages) {
+ public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
}
@Override
- public List<String> getSoundProfileAllowList() {
+ public List<String> getSoundProfileAllowList(UserHandle user) {
return new ArrayList<>();
}
@Override
- public void setSoundProfileAllowList(List<String> packages) {
+ public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
}
@Override
- public boolean isSupported() {
+ public boolean isSupported(UserHandle user) {
return false;
}
@Override
- public void setAutoPictureQualityEnabled(boolean enabled) {
+ public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
}
@Override
- public boolean isAutoPictureQualityEnabled() {
+ public boolean isAutoPictureQualityEnabled(UserHandle user) {
return false;
}
@Override
- public void setSuperResolutionEnabled(boolean enabled) {
+ public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
}
@Override
- public boolean isSuperResolutionEnabled() {
+ public boolean isSuperResolutionEnabled(UserHandle user) {
return false;
}
@Override
- public void setAutoSoundQualityEnabled(boolean enabled) {
+ public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
}
@Override
- public boolean isAutoSoundQualityEnabled() {
+ public boolean isAutoSoundQualityEnabled(UserHandle user) {
return false;
}
@Override
- public boolean isAmbientBacklightEnabled() {
+ public boolean isAmbientBacklightEnabled(UserHandle user) {
return false;
}
}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index f9145514a4e0..4b41696a4390 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -59,6 +59,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@@ -243,7 +244,7 @@ public class GroupHelper {
if (!sbn.isAppGroup()) {
sbnToBeAutogrouped = maybeGroupWithSections(record, autogroupSummaryExists);
} else {
- maybeUngroupWithSections(record);
+ maybeUngroupOnAppGrouped(record);
}
} else {
final StatusBarNotification sbn = record.getSbn();
@@ -553,11 +554,13 @@ public class GroupHelper {
}
/**
- * A non-app grouped notification has been added or updated
+ * A non-app-grouped notification has been added or updated
* Evaluate if:
* (a) an existing autogroup summary needs updated attributes
* (b) a new autogroup summary needs to be added with correct attributes
* (c) other non-app grouped children need to be moved to the autogroup
+ * (d) the notification has been updated from a groupable to a non-groupable section and needs
+ * to trigger a cleanup
*
* This method implements autogrouping with sections support.
*
@@ -567,11 +570,11 @@ public class GroupHelper {
boolean autogroupSummaryExists) {
final StatusBarNotification sbn = record.getSbn();
boolean sbnToBeAutogrouped = false;
-
final NotificationSectioner sectioner = getSection(record);
if (sectioner == null) {
+ maybeUngroupOnNonGroupableUpdate(record);
if (DEBUG) {
- Log.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+ Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
}
return false;
}
@@ -584,7 +587,6 @@ public class GroupHelper {
if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
return false;
}
-
synchronized (mAggregatedNotifications) {
ArrayMap<String, NotificationAttributes> ungrouped =
mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
@@ -601,11 +603,11 @@ public class GroupHelper {
if (ungrouped.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
if (DEBUG) {
if (ungrouped.size() >= mAutoGroupAtCount) {
- Log.i(TAG,
+ Slog.i(TAG,
"Found >=" + mAutoGroupAtCount
+ " ungrouped notifications => force grouping");
} else {
- Log.i(TAG, "Found aggregate summary => force grouping");
+ Slog.i(TAG, "Found aggregate summary => force grouping");
}
}
@@ -642,7 +644,24 @@ public class GroupHelper {
}
/**
- * A notification was added that's app grouped.
+ * A notification was added that was previously part of a valid section and needs to trigger
+ * GH state cleanup.
+ */
+ private void maybeUngroupOnNonGroupableUpdate(NotificationRecord record) {
+ maybeUngroupWithSections(record, getPreviousValidSectionKey(record));
+ }
+
+ /**
+ * A notification was added that is app-grouped.
+ */
+ private void maybeUngroupOnAppGrouped(NotificationRecord record) {
+ maybeUngroupWithSections(record, getSectionGroupKeyWithFallback(record));
+ }
+
+ /**
+ * Called when a notification is posted and is either app-grouped or was previously part of
+ * a valid section and needs to trigger GH state cleanup.
+ *
* Evaluate whether:
* (a) an existing autogroup summary needs updated attributes
* (b) if we need to remove our autogroup overlay for this notification
@@ -652,13 +671,20 @@ public class GroupHelper {
*
* And updates the internal state of un-app-grouped notifications and their flags.
*/
- private void maybeUngroupWithSections(NotificationRecord record) {
+ private void maybeUngroupWithSections(NotificationRecord record,
+ @Nullable FullyQualifiedGroupKey fullAggregateGroupKey) {
+ if (fullAggregateGroupKey == null) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping maybeUngroupWithSections for " + record
+ + " no valid section found.");
+ }
+ return;
+ }
+
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
- final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
- pkgName, getSection(record));
-
synchronized (mAggregatedNotifications) {
// if this notification still exists and has an autogroup overlay, but is now
// grouped by the app, clear the overlay
@@ -675,21 +701,22 @@ public class GroupHelper {
mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
if (DEBUG) {
- Log.i(TAG, "maybeUngroup removeAutoGroup: " + record);
+ Slog.i(TAG, "maybeUngroup removeAutoGroup: " + record);
}
mCallback.removeAutoGroup(sbn.getKey());
if (aggregatedNotificationsAttrs.isEmpty()) {
if (DEBUG) {
- Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
}
mCallback.removeAutoGroupSummary(userId, pkgName,
fullAggregateGroupKey.toString());
mAggregatedNotifications.remove(fullAggregateGroupKey);
} else {
if (DEBUG) {
- Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ Slog.i(TAG,
+ "Aggregate group not empty, updating: " + fullAggregateGroupKey);
}
updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
}
@@ -860,8 +887,15 @@ public class GroupHelper {
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
- final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
- pkgName, getSection(record));
+
+ final FullyQualifiedGroupKey fullAggregateGroupKey = getSectionGroupKeyWithFallback(record);
+ if (fullAggregateGroupKey == null) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping autogroup cleanup for " + record + " no valid section found.");
+ }
+ return;
+ }
synchronized (mAggregatedNotifications) {
ArrayMap<String, NotificationAttributes> ungrouped =
@@ -879,14 +913,15 @@ public class GroupHelper {
if (aggregatedNotificationsAttrs.isEmpty()) {
if (DEBUG) {
- Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+ Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
}
mCallback.removeAutoGroupSummary(userId, pkgName,
fullAggregateGroupKey.toString());
mAggregatedNotifications.remove(fullAggregateGroupKey);
} else {
if (DEBUG) {
- Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+ Slog.i(TAG,
+ "Aggregate group not empty, updating: " + fullAggregateGroupKey);
}
updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
}
@@ -901,6 +936,52 @@ public class GroupHelper {
}
/**
+ * Get the section key for a notification. If the section is invalid, ie. notification is not
+ * auto-groupable, then return the previous valid section, if any.
+ * @param record the notification
+ * @return a section group key, null if not found
+ */
+ @Nullable
+ private FullyQualifiedGroupKey getSectionGroupKeyWithFallback(final NotificationRecord record) {
+ final NotificationSectioner sectioner = getSection(record);
+ if (sectioner != null) {
+ return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
+ sectioner);
+ } else {
+ return getPreviousValidSectionKey(record);
+ }
+ }
+
+ /**
+ * Get the previous valid section key of a notification that may have been updated to an invalid
+ * section. This is needed in case a notification is updated as an ungroupable (invalid section)
+ * => auto-groups need to be updated/GH state cleanup.
+ * @param record the notification
+ * @return a section group key or null if not found
+ */
+ @Nullable
+ private FullyQualifiedGroupKey getPreviousValidSectionKey(final NotificationRecord record) {
+ synchronized (mAggregatedNotifications) {
+ final String recordKey = record.getKey();
+ // Search in ungrouped
+ for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ ungroupedSection : mUngroupedAbuseNotifications.entrySet()) {
+ if (ungroupedSection.getValue().containsKey(recordKey)) {
+ return ungroupedSection.getKey();
+ }
+ }
+ // Search in aggregated
+ for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+ aggregatedSection : mAggregatedNotifications.entrySet()) {
+ if (aggregatedSection.getValue().containsKey(recordKey)) {
+ return aggregatedSection.getKey();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Called when a child notification is removed, after some delay, so that this helper can
* trigger a forced grouping if the group has become sparse/singleton
* or only the summary is left.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 15af36ba66af..39eea740a902 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2594,6 +2594,7 @@ public class NotificationManagerService extends SystemService {
intent.setPackage(pkg);
intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, id);
intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, status);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
getContext().sendBroadcastAsUser(intent, UserHandle.of(userId));
});
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index dc173b124884..95aff5652bb6 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -112,9 +112,10 @@ import android.util.SparseArray;
import android.util.StatsEvent;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.FrameworkStatsLog;
@@ -279,6 +280,11 @@ public class ZenModeHelper {
mCallbacks.remove(callback);
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public List<Callback> getCallbacks() {
+ return mCallbacks;
+ }
+
public void initZenMode() {
if (DEBUG) Log.d(TAG, "initZenMode");
synchronized (mConfigLock) {
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index f79d9ef174ea..65a38ae1fcde 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -123,13 +123,6 @@ flag {
}
flag {
- name: "use_ipcdatacache_channels"
- namespace: "systemui"
- description: "Adds an IPCDataCache for notification channel/group lookups"
- bug: "331677193"
-}
-
-flag {
name: "use_ssm_user_switch_signal"
namespace: "systemui"
description: "This flag controls which signal is used to handle a user switch system event"
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS
deleted file mode 100644
index 09774f78d712..000000000000
--- a/services/core/java/com/android/server/ondeviceintelligence/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
index 8ec716077f46..871d12ee6394 100644
--- a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -20,116 +20,100 @@ import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION;
import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresPermission;
+import android.app.ActivityManagerInternal;
import android.content.Context;
+import android.os.RemoteException;
import android.os.instrumentation.ExecutableMethodFileOffsets;
import android.os.instrumentation.IDynamicInstrumentationManager;
+import android.os.instrumentation.IOffsetCallback;
import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.MethodDescriptorParser;
import android.os.instrumentation.TargetProcess;
-import com.android.internal.annotations.VisibleForTesting;
+
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import dalvik.system.VMDebug;
import java.lang.reflect.Method;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
/**
* System private implementation of the {@link IDynamicInstrumentationManager interface}.
*/
public class DynamicInstrumentationManagerService extends SystemService {
+
+ private ActivityManagerInternal mAmInternal;
+
public DynamicInstrumentationManagerService(@NonNull Context context) {
super(context);
}
@Override
public void onStart() {
+ mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService());
}
private final class BinderService extends IDynamicInstrumentationManager.Stub {
@Override
@PermissionManuallyEnforced
- public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
- @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) {
+ @RequiresPermission(value = android.Manifest.permission.DYNAMIC_INSTRUMENTATION)
+ public void getExecutableMethodFileOffsets(
+ @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor,
+ @NonNull IOffsetCallback callback) {
if (!com.android.art.flags.Flags.executableMethodFileOffsets()) {
throw new UnsupportedOperationException();
}
getContext().enforceCallingOrSelfPermission(
DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission");
+ Objects.requireNonNull(targetProcess.processName);
- if (targetProcess.processName == null
- || !targetProcess.processName.equals("system_server")) {
- throw new UnsupportedOperationException(
- "system_server is the only supported target process");
+ if (!targetProcess.processName.equals("system_server")) {
+ try {
+ mAmInternal.getExecutableMethodFileOffsets(targetProcess.processName,
+ targetProcess.pid, targetProcess.uid, methodDescriptor,
+ new IOffsetCallback.Stub() {
+ @Override
+ public void onResult(ExecutableMethodFileOffsets result) {
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+ });
+ return;
+ } catch (NoSuchElementException e) {
+ throw new IllegalArgumentException(
+ "The specified app process cannot be found." , e);
+ }
}
- Method method = parseMethodDescriptor(
+ Method method = MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(), methodDescriptor);
VMDebug.ExecutableMethodFileOffsets location =
VMDebug.getExecutableMethodFileOffsets(method);
- if (location == null) {
- return null;
- }
-
- ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
- ret.containerPath = location.getContainerPath();
- ret.containerOffset = location.getContainerOffset();
- ret.methodOffset = location.getMethodOffset();
- return ret;
- }
- }
-
- @VisibleForTesting
- static Method parseMethodDescriptor(ClassLoader classLoader,
- @NonNull MethodDescriptor descriptor) {
- try {
- Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
- Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length];
- for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) {
- String typeName = descriptor.fullyQualifiedParameters[i];
- boolean isArrayType = typeName.endsWith("[]");
- if (isArrayType) {
- typeName = typeName.substring(0, typeName.length() - 2);
+ try {
+ if (location == null) {
+ callback.onResult(null);
+ return;
}
- switch (typeName) {
- case "boolean":
- parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class;
- break;
- case "byte":
- parameters[i] = isArrayType ? byte.class.arrayType() : byte.class;
- break;
- case "char":
- parameters[i] = isArrayType ? char.class.arrayType() : char.class;
- break;
- case "short":
- parameters[i] = isArrayType ? short.class.arrayType() : short.class;
- break;
- case "int":
- parameters[i] = isArrayType ? int.class.arrayType() : int.class;
- break;
- case "long":
- parameters[i] = isArrayType ? long.class.arrayType() : long.class;
- break;
- case "float":
- parameters[i] = isArrayType ? float.class.arrayType() : float.class;
- break;
- case "double":
- parameters[i] = isArrayType ? double.class.arrayType() : double.class;
- break;
- default:
- parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType()
- : classLoader.loadClass(typeName);
- }
- }
- return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
- } catch (ClassNotFoundException | NoSuchMethodException e) {
- throw new IllegalArgumentException(
- "The specified method cannot be found. Is this descriptor valid? "
- + descriptor, e);
+ ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+ ret.containerPath = location.getContainerPath();
+ ret.containerOffset = location.getContainerOffset();
+ ret.methodOffset = location.getMethodOffset();
+ callback.onResult(ret);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to invoke result callback", e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
index 27c4e9dca586..bc0fc2b0b7d9 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -33,9 +33,10 @@ import com.android.server.ServiceThread;
public class BackgroundInstallControlCallbackHelper {
- @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
- @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
- @VisibleForTesting static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+ public static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+ public static final String FLAGGED_USER_ID_KEY = "userId";
+ public static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+
private static final String TAG = "BackgroundInstallControlCallbackHelper";
private final Handler mHandler;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 58b1e496f5f1..be2f58dc276c 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -138,7 +138,8 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerInternal;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerLocal;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -5851,10 +5852,10 @@ public class ComputerEngine implements Computer {
if (isHotword) {
return true;
}
- OnDeviceIntelligenceManagerInternal onDeviceIntelligenceManagerInternal =
- mInjector.getLocalService(OnDeviceIntelligenceManagerInternal.class);
- return onDeviceIntelligenceManagerInternal != null
- && uid == onDeviceIntelligenceManagerInternal.getInferenceServiceUid();
+ OnDeviceIntelligenceManagerLocal onDeviceIntelligenceManagerLocal =
+ LocalManagerRegistry.getManager(OnDeviceIntelligenceManagerLocal.class);
+ return onDeviceIntelligenceManagerLocal != null
+ && uid == onDeviceIntelligenceManagerLocal.getInferenceServiceUid();
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 42b8dd71c6e2..c0ddebeb9868 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -32,11 +32,13 @@ import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
import android.content.pm.dependencyinstaller.IDependencyInstallerService;
import android.content.pm.parsing.PackageLite;
+import android.os.Binder;
import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -63,12 +65,11 @@ public class InstallDependencyHelper {
private final Context mContext;
private final SharedLibrariesImpl mSharedLibraries;
private final PackageInstallerService mPackageInstallerService;
- private final Object mRemoteServiceLock = new Object();
@GuardedBy("mTrackers")
private final List<DependencyInstallTracker> mTrackers = new ArrayList<>();
-
- @GuardedBy("mRemoteServiceLock")
- private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
+ @GuardedBy("mRemoteServices")
+ private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices =
+ new ArrayMap<>();
InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries,
PackageInstallerService packageInstallerService) {
@@ -97,23 +98,28 @@ public class InstallDependencyHelper {
if (missing.isEmpty()) {
if (DEBUG) {
- Slog.i(TAG, "No missing dependency for " + pkg);
+ Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
}
// No need for dependency resolution. Move to installation directly.
callback.onResult(null);
return;
}
+ if (DEBUG) {
+ Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName()
+ + " user: " + userId);
+ }
+
if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
onError(callback, "Dependency Installer Service not found");
return;
}
IDependencyInstallerCallback serviceCallback =
- new DependencyInstallerCallbackCallOnce(handler, callback);
+ new DependencyInstallerCallbackCallOnce(handler, callback, userId);
boolean scheduleSuccess;
- synchronized (mRemoteServiceLock) {
- scheduleSuccess = mRemoteService.run(service -> {
+ synchronized (mRemoteServices) {
+ scheduleSuccess = mRemoteServices.get(userId).run(service -> {
service.onDependenciesRequired(missing,
new DependencyInstallerCallback(serviceCallback.asBinder()));
});
@@ -123,14 +129,14 @@ public class InstallDependencyHelper {
}
}
- void notifySessionComplete(int sessionId, boolean success) {
+ void notifySessionComplete(int sessionId) {
if (DEBUG) {
- Slog.i(TAG, "Session complete for " + sessionId + " result: " + success);
+ Slog.d(TAG, "Session complete for " + sessionId);
}
synchronized (mTrackers) {
List<DependencyInstallTracker> completedTrackers = new ArrayList<>();
for (DependencyInstallTracker tracker: mTrackers) {
- if (!tracker.onSessionComplete(sessionId, success)) {
+ if (!tracker.onSessionComplete(sessionId)) {
completedTrackers.add(tracker);
}
}
@@ -149,15 +155,17 @@ public class InstallDependencyHelper {
private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
Computer snapshot) {
- synchronized (mRemoteServiceLock) {
- if (mRemoteService != null) {
+ synchronized (mRemoteServices) {
+ if (mRemoteServices.containsKey(userId)) {
if (DEBUG) {
- Slog.i(TAG, "DependencyInstallerService already bound");
+ Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound");
}
return true;
}
}
+ Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId);
+
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
if (roleManager == null) {
Slog.w(TAG, "Cannot find RoleManager system service");
@@ -166,7 +174,7 @@ public class InstallDependencyHelper {
List<String> holders = roleManager.getRoleHoldersAsUser(
ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
if (holders.isEmpty()) {
- Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found");
+ Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId);
return false;
}
@@ -178,6 +186,8 @@ public class InstallDependencyHelper {
/*includeInstantApps*/ false, /*resolveForStart*/ false);
if (resolvedIntents.isEmpty()) {
+ Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user "
+ + userId);
return false;
}
@@ -206,13 +216,14 @@ public class InstallDependencyHelper {
};
- synchronized (mRemoteServiceLock) {
+ synchronized (mRemoteServices) {
// Some other thread managed to connect to the service first
- if (mRemoteService != null) {
+ if (mRemoteServices.containsKey(userId)) {
return true;
}
- mRemoteService = serviceConnector;
- mRemoteService.setServiceLifecycleCallbacks(
+ mRemoteServices.put(userId, serviceConnector);
+ // Block the lock until we connect to the service
+ serviceConnector.setServiceLifecycleCallbacks(
new ServiceConnector.ServiceLifecycleCallbacks<>() {
@Override
public void onDisconnected(@NonNull IDependencyInstallerService service) {
@@ -228,17 +239,18 @@ public class InstallDependencyHelper {
}
private void destroy() {
- synchronized (mRemoteServiceLock) {
- if (mRemoteService != null) {
- mRemoteService.unbind();
- mRemoteService = null;
+ synchronized (mRemoteServices) {
+ if (mRemoteServices.containsKey(userId)) {
+ mRemoteServices.get(userId).unbind();
+ mRemoteServices.remove(userId);
}
}
}
});
- AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+ AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect();
}
+ Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId);
return true;
}
@@ -292,79 +304,136 @@ public class InstallDependencyHelper {
private final Handler mHandler;
private final CallOnceProxy mCallback;
+ private final int mUserId;
@GuardedBy("this")
- private boolean mCalled = false;
+ private boolean mDependencyInstallerCallbackInvoked = false;
- DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback) {
+ DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId) {
mHandler = handler;
mCallback = callback;
+ mUserId = userId;
}
- // TODO(b/372862145): Consider turning the binder call to two-way so that we can
- // throw IllegalArgumentException
@Override
public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
synchronized (this) {
- if (mCalled) {
- return;
+ if (mDependencyInstallerCallbackInvoked) {
+ throw new IllegalStateException(
+ "Callback is being or has been already processed");
}
- mCalled = true;
+ mDependencyInstallerCallbackInvoked = true;
}
- ArraySet<Integer> set = new ArraySet<>();
- for (int i = 0; i < sessionIds.length; i++) {
- if (DEBUG) {
- Slog.i(TAG, "onAllDependenciesResolved called with " + sessionIds[i]);
- }
- set.add(sessionIds[i]);
- }
- DependencyInstallTracker tracker = new DependencyInstallTracker(mCallback, set);
- synchronized (mTrackers) {
- mTrackers.add(tracker);
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved started");
}
- // In case any of the session ids have already been installed, check if they
- // are valid.
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.i(TAG, "onAllDependenciesResolved cleaning up invalid sessions");
+ try {
+ // Before creating any tracker, validate the arguments
+ ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);
+
+ if (validSessionIds.isEmpty()) {
+ mCallback.onResult(null);
+ return;
}
- for (int i = 0; i < sessionIds.length; i++) {
- int sessionId = sessionIds[i];
- SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+ // Create a tracker now if there are any pending sessions remaining.
+ DependencyInstallTracker tracker = new DependencyInstallTracker(
+ mCallback, validSessionIds);
+ synchronized (mTrackers) {
+ mTrackers.add(tracker);
+ }
- // Continue waiting if session exists and hasn't passed or failed yet.
- if (sessionInfo != null && !sessionInfo.isSessionApplied
- && !sessionInfo.isSessionFailed) {
- continue;
- }
+ // By the time the tracker was created, some of the sessions in validSessionIds
+ // could have finished. Avoid waiting for them indefinitely.
+ for (int sessionId : validSessionIds) {
+ SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
- if (DEBUG) {
- Slog.i(TAG, "onAllDependenciesResolved cleaning up finished"
- + " session: " + sessionId);
+ // Don't wait for sessions that finished already
+ if (sessionInfo == null) {
+ Binder.withCleanCallingIdentity(() -> {
+ notifySessionComplete(sessionId);
+ });
}
-
- // If session info is null, we assume it to be success.
- // TODO(b/372862145): Check historical sessions to be more precise.
- boolean success = sessionInfo == null || sessionInfo.isSessionApplied;
-
- notifySessionComplete(sessionId, /*success=*/success);
}
- });
+ } catch (Exception e) {
+ // Allow calling the callback again
+ synchronized (this) {
+ mDependencyInstallerCallbackInvoked = false;
+ }
+ throw e;
+ }
}
@Override
public void onFailureToResolveAllDependencies() throws RemoteException {
synchronized (this) {
- if (mCalled) {
- return;
+ if (mDependencyInstallerCallbackInvoked) {
+ throw new IllegalStateException(
+ "Callback is being or has been already processed");
}
+ mDependencyInstallerCallbackInvoked = true;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
onError(mCallback, "Failed to resolve all dependencies automatically");
- mCalled = true;
+ });
+ }
+
+ private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
+ // Before creating any tracker, validate the arguments
+ ArraySet<Integer> validSessionIds = new ArraySet<>();
+
+ List<SessionInfo> historicalSessions = null;
+ for (int i = 0; i < sessionIds.length; i++) {
+ int sessionId = sessionIds[i];
+ SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+ // Continue waiting if session exists and hasn't passed or failed yet.
+ if (sessionInfo != null) {
+ if (sessionInfo.isSessionFailed) {
+ throw new IllegalArgumentException("Session already finished: "
+ + sessionId);
+ }
+
+ // Wait for session to finish install if it's not already successful.
+ if (!sessionInfo.isSessionApplied) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved pending session: " + sessionId);
+ }
+ validSessionIds.add(sessionId);
+ }
+
+ // An applied session found. No need to check historical session anymore.
+ continue;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "onAllDependenciesResolved cleaning up finished"
+ + " session: " + sessionId);
+ }
+
+ if (historicalSessions == null) {
+ historicalSessions = mPackageInstallerService.getHistoricalSessions(
+ mUserId).getList();
+ }
+
+ sessionInfo = historicalSessions.stream().filter(
+ s -> s.sessionId == sessionId).findFirst().orElse(null);
+
+ if (sessionInfo == null) {
+ throw new IllegalArgumentException("Failed to find session: " + sessionId);
+ }
+
+ // Historical session must have been successful, otherwise throw IAE.
+ if (!sessionInfo.isSessionApplied) {
+ throw new IllegalArgumentException("Session already finished: " + sessionId);
+ }
}
+
+ return validSessionIds;
}
}
@@ -377,6 +446,7 @@ public class InstallDependencyHelper {
// TODO(b/372862145): Determine and add support for rebooting while dependency is being resolved
private static class DependencyInstallTracker {
private final CallOnceProxy mCallback;
+ @GuardedBy("this")
private final ArraySet<Integer> mPendingSessionIds;
DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds) {
@@ -389,20 +459,13 @@ public class InstallDependencyHelper {
*
* Returns true if we still need to continue tracking.
*/
- public boolean onSessionComplete(int sessionId, boolean success) {
+ public boolean onSessionComplete(int sessionId) {
synchronized (this) {
if (!mPendingSessionIds.contains(sessionId)) {
// This had no impact on tracker, so continue tracking
return true;
}
- if (!success) {
- // If one of the dependency fails, the orig session would fail too.
- onError(mCallback, "Failed to install all dependencies");
- // TODO(b/372862145): Abandon the rest of the pending sessions.
- return false; // No point in tracking anymore
- }
-
mPendingSessionIds.remove(sessionId);
if (mPendingSessionIds.isEmpty()) {
mCallback.onResult(null);
@@ -411,6 +474,5 @@ public class InstallDependencyHelper {
return true; // Keep on tracking
}
}
-
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e5e274450655..69c6ce8ea0cd 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -229,7 +229,6 @@ final class InstallPackageHelper {
private final SharedLibrariesImpl mSharedLibraries;
private final PackageManagerServiceInjector mInjector;
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
- private final InstallDependencyHelper mInstallDependencyHelper;
private final Object mInternalLock = new Object();
@GuardedBy("mInternalLock")
@@ -240,8 +239,7 @@ final class InstallPackageHelper {
AppDataHelper appDataHelper,
RemovePackageHelper removePackageHelper,
DeletePackageHelper deletePackageHelper,
- BroadcastHelper broadcastHelper,
- InstallDependencyHelper installDependencyHelper) {
+ BroadcastHelper broadcastHelper) {
mPm = pm;
mInjector = pm.mInjector;
mAppDataHelper = appDataHelper;
@@ -255,7 +253,6 @@ final class InstallPackageHelper {
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
- mInstallDependencyHelper = installDependencyHelper;
}
/**
@@ -1208,71 +1205,68 @@ final class InstallPackageHelper {
private boolean scanInstallPackages(List<InstallRequest> requests,
Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
- // TODO(b/362840929): remove locker
- try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
- final Set<String> scannedPackages = new ArraySet<>(requests.size());
- for (InstallRequest request : requests) {
- final ParsedPackage packageToScan = request.getParsedPackage();
- if (packageToScan == null) {
- request.setError(INSTALL_FAILED_SESSION_INVALID,
- "Failed to obtain package to scan");
+ final Set<String> scannedPackages = new ArraySet<>(requests.size());
+ for (InstallRequest request : requests) {
+ final ParsedPackage packageToScan = request.getParsedPackage();
+ if (packageToScan == null) {
+ request.setError(INSTALL_FAILED_SESSION_INVALID,
+ "Failed to obtain package to scan");
+ return false;
+ }
+ request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+ final String packageName = packageToScan.getPackageName();
+ try {
+ request.onScanStarted();
+ final ScanResult scanResult = scanPackageTraced(request.getParsedPackage(),
+ request.getParseFlags(), request.getScanFlags(),
+ System.currentTimeMillis(), request.getUser(),
+ request.getAbiOverride());
+ request.setScanResult(scanResult);
+ request.onScanFinished();
+ if (!scannedPackages.add(packageName)) {
+ request.setError(
+ PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Duplicate package "
+ + packageName
+ + " in multi-package install request.");
return false;
}
- request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
- final String packageName = packageToScan.getPackageName();
- try {
- request.onScanStarted();
- final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
- request.getParseFlags(), request.getScanFlags(),
- System.currentTimeMillis(), request.getUser(),
- request.getAbiOverride());
- request.setScanResult(scanResult);
- request.onScanFinished();
- if (!scannedPackages.add(packageName)) {
- request.setError(
- PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
- "Duplicate package "
- + packageName
- + " in multi-package install request.");
- return false;
- }
- if (!checkNoAppStorageIsConsistent(
- request.getScanRequestOldPackage(), packageToScan)) {
- // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
- // signatures. Is there a better error code?
- request.setError(
- INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "Update attempted to change value of "
- + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
- return false;
- }
- final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
- final boolean isSdkLibrary = packageToScan.isSdkLibrary();
- if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
- request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
- } else {
- createdAppId.put(packageName, optimisticallyRegisterAppId(request));
- }
- versionInfos.put(packageName,
- mPm.getSettingsVersionForPackage(packageToScan));
- } catch (PackageManagerException e) {
- request.setError("Scanning Failed.", e);
+ if (!checkNoAppStorageIsConsistent(
+ request.getScanRequestOldPackage(), packageToScan)) {
+ // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
+ // signatures. Is there a better error code?
+ request.setError(
+ INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Update attempted to change value of "
+ + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
return false;
}
- if (request.isArchived()) {
- final SparseArray<String> responsibleInstallerTitles =
- PackageArchiver.getResponsibleInstallerTitles(mContext,
- mPm.snapshotComputer(), request.getInstallSource(),
- request.getUserId(), mPm.mUserManager.getUserIds());
- if (responsibleInstallerTitles == null
- || responsibleInstallerTitles.size() == 0) {
- request.setError(PackageManagerException.ofInternalError(
- "Failed to obtain the responsible installer info",
- INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
- return false;
- }
- request.setResponsibleInstallerTitles(responsibleInstallerTitles);
+ final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
+ final boolean isSdkLibrary = packageToScan.isSdkLibrary();
+ if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
+ request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
+ } else {
+ createdAppId.put(packageName, optimisticallyRegisterAppId(request));
+ }
+ versionInfos.put(packageName,
+ mPm.getSettingsVersionForPackage(packageToScan));
+ } catch (PackageManagerException e) {
+ request.setError("Scanning Failed.", e);
+ return false;
+ }
+ if (request.isArchived()) {
+ final SparseArray<String> responsibleInstallerTitles =
+ PackageArchiver.getResponsibleInstallerTitles(mContext,
+ mPm.snapshotComputer(), request.getInstallSource(),
+ request.getUserId(), mPm.mUserManager.getUserIds());
+ if (responsibleInstallerTitles == null
+ || responsibleInstallerTitles.size() == 0) {
+ request.setError(PackageManagerException.ofInternalError(
+ "Failed to obtain the responsible installer info",
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
+ return false;
}
+ request.setResponsibleInstallerTitles(responsibleInstallerTitles);
}
}
return true;
@@ -1367,13 +1361,8 @@ final class InstallPackageHelper {
}
}
}
-
- for (InstallRequest request : requests) {
- mInstallDependencyHelper.notifySessionComplete(request.getSessionId(), success);
- }
}
- @GuardedBy("mPm.mInstallLock")
private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) {
if (oldPkg == null) {
// New install, nothing to check against.
@@ -4126,14 +4115,13 @@ final class InstallPackageHelper {
}
}
- @GuardedBy("mPm.mInstallLock")
- private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
+ private ScanResult scanPackageTraced(ParsedPackage parsedPackage,
final @ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
try {
- return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
+ return scanPackageNew(parsedPackage, parseFlags, scanFlags, currentTime, user,
cpuAbiOverride);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4210,8 +4198,7 @@ final class InstallPackageHelper {
realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride);
}
- @GuardedBy("mPm.mInstallLock")
- private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
+ private ScanResult scanPackageNew(@NonNull ParsedPackage parsedPackage,
final @ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user, String cpuAbiOverride)
@@ -4242,7 +4229,7 @@ final class InstallPackageHelper {
initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
cpuAbiOverride);
- return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
+ return ScanPackageUtils.scanPackageOnly(request, mPm.mInjector, mPm.mFactoryTest,
currentTime);
}
}
@@ -4296,7 +4283,7 @@ final class InstallPackageHelper {
ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
mPm.getPlatformPackage(), true);
final ScanResult scanResult =
- ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector,
+ ScanPackageUtils.scanPackageOnly(request, mPm.mInjector,
mPm.mFactoryTest, -1L);
if (scanResult.mExistingSettingCopied
&& scanResult.mRequest.mPkgSetting != null) {
@@ -4488,7 +4475,7 @@ final class InstallPackageHelper {
final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
? System.currentTimeMillis() : 0;
- final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
+ final ScanResult scanResult = scanPackageNew(parsedPackage, parseFlags,
scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 0f245b6c2201..7229f070acf0 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -29,6 +29,7 @@ import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -638,7 +639,9 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
return NativeLibraryHelper.checkAlignmentForCompatMode(
handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride);
} catch (IOException e) {
- throw new RuntimeException(e);
+ Slog.e(PackageManagerService.TAG, "Failed to check alignment of package : "
+ + pkg.getPackageName());
+ return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 0a0882d80cc1..4ea405441030 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -18,7 +18,6 @@ package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
@@ -29,7 +28,6 @@ import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
import static com.android.server.pm.PackageManagerService.INSTANT_APP_RESOLUTION_PHASE_TWO;
-import static com.android.server.pm.PackageManagerService.INTEGRITY_VERIFICATION_COMPLETE;
import static com.android.server.pm.PackageManagerService.PACKAGE_VERIFIED;
import static com.android.server.pm.PackageManagerService.POST_INSTALL;
import static com.android.server.pm.PackageManagerService.PRUNE_UNUSED_STATIC_SHARED_LIBRARIES;
@@ -149,42 +147,6 @@ final class PackageHandler extends Handler {
break;
}
- case CHECK_PENDING_INTEGRITY_VERIFICATION: {
- final int verificationId = msg.arg1;
- final PackageVerificationState state = mPm.mPendingVerification.get(verificationId);
-
- if (state != null && !state.isIntegrityVerificationComplete()) {
- final VerifyingSession verifyingSession = state.getVerifyingSession();
- final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
- String errorMsg = "Integrity verification timed out for " + originUri;
- Slog.i(TAG, errorMsg);
-
- state.setIntegrityVerificationResult(
- getDefaultIntegrityVerificationResponse());
-
- if (getDefaultIntegrityVerificationResponse()
- == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
- Slog.i(TAG, "Integrity check times out, continuing with " + originUri);
- } else {
- verifyingSession.setReturnCode(
- PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
- errorMsg);
- }
-
- if (state.areAllVerificationsComplete()) {
- mPm.mPendingVerification.remove(verificationId);
- }
-
- Trace.asyncTraceEnd(
- TRACE_TAG_PACKAGE_MANAGER,
- "integrity_verification",
- verificationId);
-
- verifyingSession.handleIntegrityVerificationFinished();
- }
- break;
- }
case PACKAGE_VERIFIED: {
final int verificationId = msg.arg1;
@@ -205,42 +167,6 @@ final class PackageHandler extends Handler {
break;
}
- case INTEGRITY_VERIFICATION_COMPLETE: {
- final int verificationId = msg.arg1;
-
- final PackageVerificationState state = mPm.mPendingVerification.get(verificationId);
- if (state == null) {
- Slog.w(TAG, "Integrity verification with id " + verificationId
- + " not found. It may be invalid or overridden by verifier");
- break;
- }
-
- final int response = (Integer) msg.obj;
- final VerifyingSession verifyingSession = state.getVerifyingSession();
- final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
- state.setIntegrityVerificationResult(response);
-
- if (response == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
- Slog.i(TAG, "Integrity check passed for " + originUri);
- } else {
- verifyingSession.setReturnCode(
- PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
- "Integrity check failed for " + originUri);
- }
-
- if (state.areAllVerificationsComplete()) {
- mPm.mPendingVerification.remove(verificationId);
- }
-
- Trace.asyncTraceEnd(
- TRACE_TAG_PACKAGE_MANAGER,
- "integrity_verification",
- verificationId);
-
- verifyingSession.handleIntegrityVerificationFinished();
- break;
- }
case INSTANT_APP_RESOLUTION_PHASE_TWO: {
InstantAppResolver.doInstantAppResolutionPhaseTwo(mPm.mContext,
mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 47b785040d44..e1fcc6650650 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -2329,6 +2329,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ if (Flags.sdkDependencyInstaller()) {
+ mInstallDependencyHelper.notifySessionComplete(session.sessionId);
+ }
+
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
appIconFile.delete();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8f8802e66004..891d66a5d238 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -61,6 +61,7 @@ import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadat
import android.Manifest;
import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -188,6 +189,7 @@ import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.art.ArtManagedInstallFileHelper;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.pkg.AndroidPackage;
@@ -852,7 +854,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
if (file.getName().endsWith(V4Signature.EXT)) return false;
if (isAppMetadata(file)) return false;
- if (DexMetadataHelper.isDexMetadataFile(file)) return false;
+ if (com.android.art.flags.Flags.artServiceV3()) {
+ if (ArtManagedInstallFileHelper.isArtManaged(file.getPath())) return false;
+ } else {
+ if (DexMetadataHelper.isDexMetadataFile(file)) return false;
+ }
if (VerityUtils.isFsveritySignatureFile(file)) return false;
if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false;
return true;
@@ -876,6 +882,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return true;
}
};
+ private static final FileFilter sArtManagedFilter = new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !file.isDirectory() && com.android.art.flags.Flags.artServiceV3()
+ && ArtManagedInstallFileHelper.isArtManaged(file.getPath());
+ }
+ };
static boolean isDataLoaderInstallation(SessionParams params) {
return params.dataLoaderParams != null;
@@ -1607,6 +1620,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
+ private List<String> getArtManagedFilePathsLocked() {
+ String[] names = getNamesLocked();
+ ArrayList<String> result = new ArrayList<>(names.length);
+ for (String name : names) {
+ File file = new File(stageDir, name);
+ if (sArtManagedFilter.accept(file)) {
+ result.add(file.getPath());
+ }
+ }
+ return result;
+ }
+
+ @GuardedBy("mLock")
private void enableFsVerityToAddedApksWithIdsig() throws PackageManagerException {
try {
List<File> files = getAddedApksLocked();
@@ -3453,7 +3479,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final File targetFile = new File(stageDir, targetName);
- resolveAndStageFileLocked(addedFile, targetFile, null);
+ resolveAndStageFileLocked(addedFile, targetFile, null, List.of() /* artManagedFilePaths */);
mResolvedBaseFile = targetFile;
// Populate package name of the apex session
@@ -3546,6 +3572,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
stageDir.getAbsolutePath()));
}
+ final List<String> artManagedFilePaths = getArtManagedFilePathsLocked();
// Verify that all staged packages are internally consistent
final ArraySet<String> stagedSplits = new ArraySet<>();
@@ -3602,7 +3629,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final File targetFile = new File(stageDir, targetName);
if (!isArchivedInstallation()) {
final File sourceFile = new File(apk.getPath());
- resolveAndStageFileLocked(sourceFile, targetFile, apk.getSplitName());
+ resolveAndStageFileLocked(
+ sourceFile, targetFile, apk.getSplitName(), artManagedFilePaths);
}
// Base is coming from session
@@ -3763,7 +3791,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Inherit base if not overridden.
if (mResolvedBaseFile == null) {
mResolvedBaseFile = new File(appInfo.getBaseCodePath());
- inheritFileLocked(mResolvedBaseFile);
+ inheritFileLocked(mResolvedBaseFile, artManagedFilePaths);
// Collect the requiredSplitTypes from base
CollectionUtils.addAll(requiredSplitTypes, existing.getBaseRequiredSplitTypes());
} else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
@@ -3782,7 +3810,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean splitRemoved = removeSplitList.contains(splitName);
final boolean splitReplaced = stagedSplits.contains(splitName);
if (!splitReplaced && !splitRemoved) {
- inheritFileLocked(splitFile);
+ inheritFileLocked(splitFile, artManagedFilePaths);
// Collect the requiredSplitTypes and staged splitTypes from splits
CollectionUtils.addAll(requiredSplitTypes,
existing.getRequiredSplitTypes()[i]);
@@ -3968,6 +3996,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
DexMetadataHelper.isFsVerityRequired());
}
+ @FlaggedApi(com.android.art.flags.Flags.FLAG_ART_SERVICE_V3)
+ @GuardedBy("mLock")
+ private void maybeStageArtManagedInstallFilesLocked(File origFile, File targetFile,
+ List<String> artManagedFilePaths) throws PackageManagerException {
+ for (String path : ArtManagedInstallFileHelper.filterPathsForApk(
+ artManagedFilePaths, origFile.getPath())) {
+ File artManagedFile = new File(path);
+ if (!FileUtils.isValidExtFilename(artManagedFile.getName())) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_INVALID_APK, "Invalid filename: " + artManagedFile);
+ }
+ File targetArtManagedFile = new File(
+ ArtManagedInstallFileHelper.getTargetPathForApk(path, targetFile.getPath()));
+ stageFileLocked(artManagedFile, targetArtManagedFile);
+ }
+ }
+
private IncrementalFileStorages getIncrementalFileStorages() {
synchronized (mLock) {
return mIncrementalFileStorages;
@@ -4065,8 +4110,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
- private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName)
- throws PackageManagerException {
+ private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName,
+ List<String> artManagedFilePaths) throws PackageManagerException {
stageFileLocked(origFile, targetFile);
// Stage APK's fs-verity signature if present.
@@ -4077,8 +4122,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
&& VerityUtils.isFsVeritySupported()) {
maybeStageV4SignatureLocked(origFile, targetFile);
}
- // Stage dex metadata (.dm) and corresponding fs-verity signature if present.
- maybeStageDexMetadataLocked(origFile, targetFile);
+ // Stage ART managed install files (e.g., dex metadata (.dm)) and corresponding fs-verity
+ // signature if present.
+ if (com.android.art.flags.Flags.artServiceV3()) {
+ maybeStageArtManagedInstallFilesLocked(origFile, targetFile, artManagedFilePaths);
+ } else {
+ maybeStageDexMetadataLocked(origFile, targetFile);
+ }
// Stage checksums (.digests) if present.
maybeStageDigestsLocked(origFile, targetFile, splitName);
}
@@ -4103,7 +4153,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
- private void inheritFileLocked(File origFile) {
+ private void inheritFileLocked(File origFile, List<String> artManagedFilePaths) {
mResolvedInheritedFiles.add(origFile);
maybeInheritFsveritySignatureLocked(origFile);
@@ -4111,12 +4161,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
maybeInheritV4SignatureLocked(origFile);
}
- // Inherit the dex metadata if present.
- final File dexMetadataFile =
- DexMetadataHelper.findDexMetadataForFile(origFile);
- if (dexMetadataFile != null) {
- mResolvedInheritedFiles.add(dexMetadataFile);
- maybeInheritFsveritySignatureLocked(dexMetadataFile);
+ // Inherit ART managed install files (e.g., dex metadata (.dm)) if present.
+ if (com.android.art.flags.Flags.artServiceV3()) {
+ for (String path : ArtManagedInstallFileHelper.filterPathsForApk(
+ artManagedFilePaths, origFile.getPath())) {
+ File artManagedFile = new File(path);
+ mResolvedInheritedFiles.add(artManagedFile);
+ maybeInheritFsveritySignatureLocked(artManagedFile);
+ }
+ } else {
+ final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile);
+ if (dexMetadataFile != null) {
+ mResolvedInheritedFiles.add(dexMetadataFile);
+ maybeInheritFsveritySignatureLocked(dexMetadataFile);
+ }
}
// Inherit the digests if present.
final File digestsFile = ApkChecksums.findDigestsForFile(origFile);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 040b1943b23d..65bb701563a8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -923,8 +923,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
static final int ENABLE_ROLLBACK_TIMEOUT = 22;
static final int DEFERRED_NO_KILL_POST_DELETE = 23;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
- static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
- static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
+ // static final int UNUSED = 25;
+ // static final int UNUSED = 26;
static final int DOMAIN_VERIFICATION = 27;
static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
@@ -2118,8 +2118,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
mBroadcastHelper);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper,
- mDeletePackageHelper, mBroadcastHelper,
- injector.getPackageInstallerService().getInstallDependencyHelper());
+ mDeletePackageHelper, mBroadcastHelper);
mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
mInjector.getUserManagerInternal(), mDeletePackageHelper);
@@ -7125,12 +7124,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return mSettings.isPermissionUpgradeNeeded(userId);
}
+ @Deprecated
@Override
public void setIntegrityVerificationResult(int verificationId, int verificationResult) {
- final Message msg = mHandler.obtainMessage(INTEGRITY_VERIFICATION_COMPLETE);
- msg.arg1 = verificationId;
- msg.obj = verificationResult;
- mHandler.sendMessage(msg);
+ // Do nothing.
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 52e8c52fe6af..ef49f49cf040 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -34,6 +34,7 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -73,22 +74,16 @@ public class PackageMonitorCallbackHelper {
}
public void onUserRemoved(int userId) {
- ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = null;
+ final ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = new ArrayList<>();
synchronized (mLock) {
- int registerCount = mCallbacks.getRegisteredCallbackCount();
- for (int i = 0; i < registerCount; i++) {
- RegisterUser registerUser =
- (RegisterUser) mCallbacks.getRegisteredCallbackCookie(i);
+ mCallbacks.broadcast((callback, user) -> {
+ RegisterUser registerUser = (RegisterUser) user;
if (registerUser.getUserId() == userId) {
- IRemoteCallback callback = mCallbacks.getRegisteredCallbackItem(i);
- if (targetUnRegisteredCallbacks == null) {
- targetUnRegisteredCallbacks = new ArrayList<>();
- }
targetUnRegisteredCallbacks.add(callback);
}
- }
+ });
}
- if (targetUnRegisteredCallbacks != null && targetUnRegisteredCallbacks.size() > 0) {
+ if (!targetUnRegisteredCallbacks.isEmpty()) {
int count = targetUnRegisteredCallbacks.size();
for (int i = 0; i < count; i++) {
unregisterPackageMonitorCallback(targetUnRegisteredCallbacks.get(i));
@@ -202,21 +197,13 @@ public class PackageMonitorCallbackHelper {
private void doNotifyCallbacksByIntent(Intent intent, int userId,
int[] broadcastAllowList, Handler handler) {
- RemoteCallbackList<IRemoteCallback> callbacks;
- synchronized (mLock) {
- callbacks = mCallbacks;
- }
- doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+ doNotifyCallbacks(intent, userId, broadcastAllowList, handler,
null /* filterExtrasFunction */);
}
private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
SparseArray<int[]> broadcastAllowList, Handler handler,
BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
- RemoteCallbackList<IRemoteCallback> callbacks;
- synchronized (mLock) {
- callbacks = mCallbacks;
- }
for (int userId : userIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
@@ -232,48 +219,58 @@ public class PackageMonitorCallbackHelper {
final int[] allowUids =
broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
- doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
+ doNotifyCallbacks(intent, userId, allowUids, handler, filterExtrasFunction);
}
}
- private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
- Intent intent, int userId, int[] allowUids, Handler handler,
+ private void doNotifyCallbacks(Intent intent, int userId, int[] allowUids, Handler handler,
BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
- handler.post(() -> callbacks.broadcast((callback, user) -> {
- RegisterUser registerUser = (RegisterUser) user;
- if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
- != userId)) {
- return;
- }
- int registerUid = registerUser.getUid();
- if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
- && !ArrayUtils.contains(allowUids, registerUid)) {
- if (DEBUG) {
- Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + ", uid " + registerUid);
- }
- return;
- }
- Intent newIntent = intent;
- if (filterExtrasFunction != null) {
- final Bundle extras = intent.getExtras();
- if (extras != null) {
- final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
- if (filteredExtras == null) {
- // caller is unable to access this intent
+ handler.post(() -> {
+ final ArrayList<Pair<IRemoteCallback, Intent>> target = new ArrayList<>();
+ synchronized (mLock) {
+ mCallbacks.broadcast((callback, user) -> {
+ RegisterUser registerUser = (RegisterUser) user;
+ if ((registerUser.getUserId() != UserHandle.USER_ALL)
+ && (registerUser.getUserId() != userId)) {
+ return;
+ }
+ int registerUid = registerUser.getUid();
+ if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID)
+ && !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
- Slog.w(TAG,
- "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + " because null filteredExtras");
+ Slog.w(TAG, "Skip invoke PackageMonitorCallback for "
+ + intent.getAction() + ", uid " + registerUid);
}
return;
}
- newIntent = new Intent(newIntent);
- newIntent.replaceExtras(filteredExtras);
- }
+ Intent newIntent = intent;
+ if (filterExtrasFunction != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final Bundle filteredExtras =
+ filterExtrasFunction.apply(registerUid, extras);
+ if (filteredExtras == null) {
+ // caller is unable to access this intent
+ if (DEBUG) {
+ Slog.w(TAG,
+ "Skip invoke PackageMonitorCallback for "
+ + intent.getAction()
+ + " because null filteredExtras");
+ }
+ return;
+ }
+ newIntent = new Intent(newIntent);
+ newIntent.replaceExtras(filteredExtras);
+ }
+ }
+ target.add(new Pair<>(callback, newIntent));
+ });
+ }
+ for (int i = 0; i < target.size(); i++) {
+ Pair<IRemoteCallback, Intent> p = target.get(i);
+ invokeCallback(p.first, p.second);
}
- invokeCallback(callback, newIntent);
- }));
+ });
}
private void invokeCallback(IRemoteCallback callback, Intent intent) {
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index 0b6ccc41d956..63c2ee2e5454 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -43,8 +43,6 @@ class PackageVerificationState {
private boolean mRequiredVerificationPassed;
- private boolean mIntegrityVerificationComplete;
-
/**
* Create a new package verification state where {@code requiredVerifierUid} is the user ID for
* the package that must reply affirmative before things can continue.
@@ -213,15 +211,7 @@ class PackageVerificationState {
return mExtendedTimeoutUids.get(uid, false);
}
- void setIntegrityVerificationResult(int code) {
- mIntegrityVerificationComplete = true;
- }
-
- boolean isIntegrityVerificationComplete() {
- return mIntegrityVerificationComplete;
- }
-
boolean areAllVerificationsComplete() {
- return mIntegrityVerificationComplete && isVerificationComplete();
+ return isVerificationComplete();
}
}
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 854e142c7c2f..ec91da90729b 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -213,6 +213,7 @@ public class SaferIntentUtils {
* CTS tests. The code in this method shall properly avoid control flows using these arguments.
*/
public static void blockNullAction(IntentArgs args, List componentList) {
+ if (args.intent.getAction() != null) return;
if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return;
final Computer computer = (Computer) args.snapshot;
@@ -235,14 +236,11 @@ public class SaferIntentUtils {
}
final ParsedMainComponent comp = infoToComponent(
resolveInfo.getComponentInfo(), resolver, args.isReceiver);
- if (comp != null && !comp.getIntents().isEmpty()
- && args.intent.getAction() == null) {
+ if (comp != null && !comp.getIntents().isEmpty()) {
match = false;
}
} else if (c instanceof IntentFilter) {
- if (args.intent.getAction() == null) {
- match = false;
- }
+ match = false;
}
if (!match) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 2665196bf3be..5c8042007ec4 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -75,7 +75,6 @@ import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import android.util.jar.StrictJarFile;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
@@ -120,10 +119,9 @@ final class ScanPackageUtils {
* @param currentTime The current time, in millis
* @return The results of the scan
*/
- @GuardedBy("mPm.mInstallLock")
@VisibleForTesting
@NonNull
- public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
+ public static ScanResult scanPackageOnly(@NonNull ScanRequest request,
PackageManagerServiceInjector injector,
boolean isUnderFactoryTest, long currentTime)
throws PackageManagerException {
@@ -445,7 +443,12 @@ final class ScanPackageUtils {
pkgSetting.getLegacyNativeLibraryPath(),
parsedPackage.isNativeLibraryRootRequiresIsa(),
pkgSetting.getCpuAbiOverride());
- pkgSetting.setPageSizeAppCompatFlags(mode);
+ if (mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ pkgSetting.setPageSizeAppCompatFlags(mode);
+ } else {
+ Slog.e(TAG, "Error occurred while checking alignment of package : "
+ + parsedPackage.getPackageName());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 94b49e538621..1fda4782fc86 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -165,6 +165,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUpdateSelfTestCases",
"file_patterns": [
"core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f7eb29fe3ee9..542ae8eb9207 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -28,7 +28,6 @@ import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
@@ -87,11 +86,6 @@ final class VerifyingSession {
* Whether verification is enabled by default.
*/
private static final boolean DEFAULT_VERIFY_ENABLE = true;
-
- /**
- * Whether integrity verification is enabled by default.
- */
- private static final boolean DEFAULT_INTEGRITY_VERIFY_ENABLE = true;
/**
* The default maximum time to wait for the integrity verification to return in
* milliseconds.
@@ -129,7 +123,6 @@ final class VerifyingSession {
private final boolean mUserActionRequired;
private final int mUserActionRequiredType;
private boolean mWaitForVerificationToComplete;
- private boolean mWaitForIntegrityVerificationToComplete;
private boolean mWaitForEnableRollbackToComplete;
private int mRet = PackageManager.INSTALL_SUCCEEDED;
private String mErrorMessage = null;
@@ -217,7 +210,6 @@ final class VerifyingSession {
new PackageVerificationState(this);
mPm.mPendingVerification.append(verificationId, verificationState);
- sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
sendPackageVerificationRequest(
verificationId, pkgLite, verificationState);
@@ -270,89 +262,6 @@ final class VerifyingSession {
mPm.mHandler.sendMessageDelayed(msg, rollbackTimeout);
}
- /**
- * Send a request to check the integrity of the package.
- */
- void sendIntegrityVerificationRequest(
- int verificationId,
- PackageInfoLite pkgLite,
- PackageVerificationState verificationState) {
- if (!isIntegrityVerificationEnabled()) {
- // Consider the integrity check as passed.
- verificationState.setIntegrityVerificationResult(
- PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- return;
- }
-
- final Intent integrityVerification =
- new Intent(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
-
- integrityVerification.setDataAndType(Uri.fromFile(new File(mOriginInfo.mResolvedPath)),
- PACKAGE_MIME_TYPE);
-
- final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND;
- integrityVerification.addFlags(flags);
-
- integrityVerification.putExtra(EXTRA_VERIFICATION_ID, verificationId);
- integrityVerification.putExtra(EXTRA_PACKAGE_NAME, pkgLite.packageName);
- integrityVerification.putExtra(EXTRA_VERSION_CODE, pkgLite.versionCode);
- integrityVerification.putExtra(EXTRA_LONG_VERSION_CODE, pkgLite.getLongVersionCode());
- populateInstallerExtras(integrityVerification);
-
- // send to integrity component only.
- integrityVerification.setPackage("android");
-
- final BroadcastOptions options = BroadcastOptions.makeBasic();
-
- mPm.mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM,
- /* receiverPermission= */ null,
- /* appOp= */ AppOpsManager.OP_NONE,
- /* options= */ options.toBundle(),
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Message msg =
- mPm.mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION);
- msg.arg1 = verificationId;
- mPm.mHandler.sendMessageDelayed(msg, getIntegrityVerificationTimeout());
- }
- }, /* scheduler= */ null,
- /* initialCode= */ 0,
- /* initialData= */ null,
- /* initialExtras= */ null);
-
- Trace.asyncTraceBegin(
- TRACE_TAG_PACKAGE_MANAGER, "integrity_verification", verificationId);
-
- // stop the copy until verification succeeds.
- mWaitForIntegrityVerificationToComplete = true;
- }
-
-
- /**
- * Get the integrity verification timeout.
- *
- * @return verification timeout in milliseconds
- */
- private long getIntegrityVerificationTimeout() {
- long timeout = Settings.Global.getLong(mPm.mContext.getContentResolver(),
- Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
- DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
- // The setting can be used to increase the timeout but not decrease it, since that is
- // equivalent to disabling the integrity component.
- return Math.max(timeout, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
- }
-
- /**
- * Check whether or not integrity verification has been enabled.
- */
- private boolean isIntegrityVerificationEnabled() {
- // We are not exposing this as a user-configurable setting because we don't want to provide
- // an easy way to get around the integrity check.
- return DEFAULT_INTEGRITY_VERIFY_ENABLE;
- }
/**
* Send a request to verifier(s) to verify the package if necessary.
@@ -827,11 +736,6 @@ final class VerifyingSession {
handleReturnCode();
}
- void handleIntegrityVerificationFinished() {
- mWaitForIntegrityVerificationToComplete = false;
- handleReturnCode();
- }
-
void handleRollbackEnabled() {
// TODO(b/112431924): Consider halting the install if we
// couldn't enable rollback.
@@ -840,7 +744,7 @@ final class VerifyingSession {
}
void handleReturnCode() {
- if (mWaitForVerificationToComplete || mWaitForIntegrityVerificationToComplete
+ if (mWaitForVerificationToComplete
|| mWaitForEnableRollbackToComplete) {
return;
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index e49dc8250bc7..976999cf6ae0 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -426,6 +426,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
private static final int TRON_COMPILATION_REASON_VDEX = 24;
private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
+ private static final int TRON_COMPILATION_REASON_CLOUD = 26;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -460,6 +461,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
return TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
case "install-bulk-secondary-downgraded" :
return TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ case "cloud":
+ return TRON_COMPILATION_REASON_CLOUD;
// These are special markers for dex metadata installation that do not
// have an equivalent as a system property.
case "install" + DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION :
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index e9cb279439a6..e989d6875d15 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -40,7 +40,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
@@ -351,22 +351,22 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Override
public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl) {
+ @Nullable String message, boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+ Boolean, Integer, SyncNotedAppOp> superImpl) {
if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
}
@Override
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index ecffd382f542..33210e28281e 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -53,7 +53,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.LocalServices;
@@ -246,11 +246,12 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, int virtualDeviceId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String,
- Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+ boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String,
+ Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) {
return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
}
@Override
diff --git a/services/core/java/com/android/server/policy/EventLogTags.logtags b/services/core/java/com/android/server/policy/EventLogTags.logtags
index 75633820d01f..a4b6472fbe62 100644
--- a/services/core/java/com/android/server/policy/EventLogTags.logtags
+++ b/services/core/java/com/android/server/policy/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.policy
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8e7302322d47..f1a481155458 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4081,6 +4081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -4164,6 +4165,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
return true;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+ case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
if (complete) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
deviceId, SystemClock.uptimeMillis(),
@@ -4426,9 +4428,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
float minLinearBrightness = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c573293cbf48..36bc0b93cd7c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -6099,16 +6099,28 @@ public final class PowerManagerService extends SystemService
}
}
- public float getBrightnessConstraint(int constraint) {
+ @Override
+ public float getBrightnessConstraint(
+ int displayId, @PowerManager.BrightnessConstraint int constraint) {
+ DisplayInfo info = null;
+ if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()
+ && mDisplayManagerInternal != null) {
+ info = mDisplayManagerInternal.getDisplayInfo(displayId);
+ }
switch (constraint) {
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM:
- return mScreenBrightnessMinimum;
+ return info != null && isValidBrightnessValue(info.brightnessMinimum)
+ ? info.brightnessMinimum : mScreenBrightnessMinimum;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM:
- return mScreenBrightnessMaximum;
+ return info != null && isValidBrightnessValue(info.brightnessMaximum)
+ ? info.brightnessMaximum : mScreenBrightnessMaximum;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT:
- return mScreenBrightnessDefault;
+ return info != null && isValidBrightnessValue(info.brightnessDefault)
+ ? info.brightnessDefault : mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
- return mScreenBrightnessDim;
+ return android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && info != null && isValidBrightnessValue(info.brightnessDim)
+ ? info.brightnessDim : mScreenBrightnessDim;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index aba15c83f907..987a84994451 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -23,6 +23,7 @@ import static com.android.server.power.hint.Flags.adpfSessionTag;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
import static com.android.server.power.hint.Flags.resetOnForkEnabled;
+import android.adpf.ISessionManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -40,6 +41,7 @@ import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
import android.hardware.power.WorkDuration;
import android.os.Binder;
import android.os.CpuHeadroomParamsInternal;
@@ -102,7 +104,6 @@ public final class HintManagerService extends SystemService {
// The minimum interval between the headroom calls as rate limiting.
private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
- private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
@VisibleForTesting final long mHintSessionPreferredRate;
@@ -180,6 +181,7 @@ public final class HintManagerService extends SystemService {
private final IPower mPowerHal;
private int mPowerHalVersion;
+ private SupportInfo mSupportInfo = null;
private final PackageManager mPackageManager;
private boolean mUsesFmq;
@@ -193,6 +195,7 @@ public final class HintManagerService extends SystemService {
private final Object mCpuHeadroomLock = new Object();
+ private ISessionManager mSessionManager;
// this cache tracks the expiration time of the items and performs cleanup on lookup
private static class HeadroomCache<K, V> {
@@ -246,13 +249,11 @@ public final class HintManagerService extends SystemService {
@GuardedBy("mCpuHeadroomLock")
private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
- private final long mCpuHeadroomIntervalMillis;
private final Object mGpuHeadroomLock = new Object();
@GuardedBy("mGpuHeadroomLock")
private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
- private final long mGpuHeadroomIntervalMillis;
// these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
private final int mDefaultCpuHeadroomCalculationWindowMillis;
@@ -294,79 +295,40 @@ public final class HintManagerService extends SystemService {
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
mUsesFmq = false;
- long cpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
- long gpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
if (mPowerHal != null) {
- try {
- mPowerHalVersion = mPowerHal.getInterfaceVersion();
- if (mPowerHal.getInterfaceVersion() >= 6) {
- if (SystemProperties.getBoolean(PROPERTY_USE_HAL_HEADROOMS, true)) {
- cpuHeadroomIntervalMillis = checkCpuHeadroomSupport();
- gpuHeadroomIntervalMillis = checkGpuHeadroomSupport();
- }
- }
- } catch (RemoteException e) {
- throw new IllegalStateException("Could not contact PowerHAL!", e);
- }
+ mSupportInfo = getSupportInfo();
}
- mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
mDefaultCpuHeadroomCalculationWindowMillis =
new CpuHeadroomParamsInternal().calculationWindowMillis;
mDefaultGpuHeadroomCalculationWindowMillis =
new GpuHeadroomParamsInternal().calculationWindowMillis;
- mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
- if (mCpuHeadroomIntervalMillis > 0) {
- mCpuHeadroomCache = new HeadroomCache<>(2, mCpuHeadroomIntervalMillis);
+ if (mSupportInfo.headroom.isCpuSupported) {
+ mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
} else {
mCpuHeadroomCache = null;
}
- if (mGpuHeadroomIntervalMillis > 0) {
- mGpuHeadroomCache = new HeadroomCache<>(2, mGpuHeadroomIntervalMillis);
+ if (mSupportInfo.headroom.isGpuSupported) {
+ mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
} else {
mGpuHeadroomCache = null;
}
}
- private long checkCpuHeadroomSupport() {
- final CpuHeadroomParams params = new CpuHeadroomParams();
- params.tids = new int[]{Process.myPid()};
+ SupportInfo getSupportInfo() {
try {
- synchronized (mCpuHeadroomLock) {
- final CpuHeadroomResult ret = mPowerHal.getCpuHeadroom(params);
- if (ret != null && ret.getTag() == CpuHeadroomResult.globalHeadroom
- && !Float.isNaN(ret.getGlobalHeadroom())) {
- return Math.max(
- DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
- mPowerHal.getCpuHeadroomMinIntervalMillis());
- }
+ mPowerHalVersion = mPowerHal.getInterfaceVersion();
+ if (mPowerHalVersion >= 6) {
+ return mPowerHal.getSupportInfo();
}
-
- } catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getCpuHeadroom HAL API is not supported, params: " + params, e);
} catch (RemoteException e) {
- Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API, params: " + params, e);
+ throw new IllegalStateException("Could not contact PowerHAL!", e);
}
- return HEADROOM_INTERVAL_UNSUPPORTED;
- }
- private long checkGpuHeadroomSupport() {
- final GpuHeadroomParams params = new GpuHeadroomParams();
- try {
- synchronized (mGpuHeadroomLock) {
- final GpuHeadroomResult ret = mPowerHal.getGpuHeadroom(params);
- if (ret != null && ret.getTag() == GpuHeadroomResult.globalHeadroom && !Float.isNaN(
- ret.getGlobalHeadroom())) {
- return Math.max(
- DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
- mPowerHal.getGpuHeadroomMinIntervalMillis());
- }
- }
- } catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getGpuHeadroom HAL API is not supported, params: " + params, e);
- } catch (RemoteException e) {
- Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API, params: " + params, e);
- }
- return HEADROOM_INTERVAL_UNSUPPORTED;
+ SupportInfo supportInfo = new SupportInfo();
+ supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+ supportInfo.headroom.isCpuSupported = false;
+ supportInfo.headroom.isGpuSupported = false;
+ return supportInfo;
}
private ServiceThread createCleanUpThread() {
@@ -555,7 +517,7 @@ public final class HintManagerService extends SystemService {
return targetDurations;
}
}
- private boolean isHalSupported() {
+ private boolean isHintSessionSupported() {
return mHintSessionPreferredRate != -1;
}
@@ -818,6 +780,23 @@ public final class HintManagerService extends SystemService {
for (int i = tokenMap.size() - 1; i >= 0; i--) {
// Will remove the session from tokenMap
ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+ IntArray closedSessionsForSf = new IntArray();
+ // Batch the closure call to SF for all the sessions that die
+ for (int j = sessionSet.size() - 1; j >= 0; j--) {
+ AppHintSession session = sessionSet.valueAt(j);
+ if (session.isTrackedBySf()) {
+ // Mark it as untracked so we don't untrack again on close
+ session.setTrackedBySf(false);
+ closedSessionsForSf.add(session.getSessionId());
+ }
+ }
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to communicate with SessionManager");
+ }
+ }
for (int j = sessionSet.size() - 1; j >= 0; j--) {
sessionSet.valueAt(j).close();
}
@@ -1248,7 +1227,7 @@ public final class HintManagerService extends SystemService {
public IHintSession createHintSessionWithConfig(@NonNull IBinder token,
@SessionTag int tag, SessionCreationConfig creationConfig,
SessionConfig config) {
- if (!isHalSupported()) {
+ if (!isHintSessionSupported()) {
throw new UnsupportedOperationException("PowerHAL is not supported!");
}
@@ -1350,9 +1329,9 @@ public final class HintManagerService extends SystemService {
}
}
- final long sessionId = config.id != -1 ? config.id : halSessionPtr;
+ final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr;
logPerformanceHintSessionAtom(
- callingUid, sessionId, durationNanos, tids, tag);
+ callingUid, sessionIdForTracing, durationNanos, tids, tag);
synchronized (mSessionSnapshotMapLock) {
// Update session snapshot upon session creation
@@ -1362,8 +1341,12 @@ public final class HintManagerService extends SystemService {
}
AppHintSession hs = null;
synchronized (mLock) {
+ Integer configId = null;
+ if (config.id != -1) {
+ configId = new Integer((int) config.id);
+ }
hs = new AppHintSession(callingUid, callingTgid, tag, tids,
- token, halSessionPtr, durationNanos);
+ token, halSessionPtr, durationNanos, configId);
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
mActiveSessions.get(callingUid);
if (tokenMap == null) {
@@ -1390,6 +1373,11 @@ public final class HintManagerService extends SystemService {
}
}
+ if (creationConfig.layerTokens != null
+ && creationConfig.layerTokens.length > 0) {
+ hs.associateToLayers(creationConfig.layerTokens);
+ }
+
synchronized (mThreadsUsageObject) {
mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>());
ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
@@ -1460,14 +1448,13 @@ public final class HintManagerService extends SystemService {
@Override
public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
- if (mCpuHeadroomIntervalMillis <= 0) {
+ if (!mSupportInfo.headroom.isCpuSupported) {
throw new UnsupportedOperationException();
}
final CpuHeadroomParams halParams = new CpuHeadroomParams();
halParams.tids = new int[]{Binder.getCallingPid()};
halParams.calculationType = params.calculationType;
halParams.calculationWindowMillis = params.calculationWindowMillis;
- halParams.selectionType = params.selectionType;
if (params.usesDeviceHeadroom) {
halParams.tids = new int[]{};
} else if (params.tids != null && params.tids.length > 0) {
@@ -1516,7 +1503,7 @@ public final class HintManagerService extends SystemService {
@Override
public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
- if (mGpuHeadroomIntervalMillis <= 0) {
+ if (!mSupportInfo.headroom.isGpuSupported) {
throw new UnsupportedOperationException();
}
final GpuHeadroomParams halParams = new GpuHeadroomParams();
@@ -1551,18 +1538,27 @@ public final class HintManagerService extends SystemService {
@Override
public long getCpuHeadroomMinIntervalMillis() {
- if (mCpuHeadroomIntervalMillis <= 0) {
+ if (!mSupportInfo.headroom.isCpuSupported) {
throw new UnsupportedOperationException();
}
- return mCpuHeadroomIntervalMillis;
+ return mSupportInfo.headroom.cpuMinIntervalMillis;
}
@Override
public long getGpuHeadroomMinIntervalMillis() {
- if (mGpuHeadroomIntervalMillis <= 0) {
+ if (!mSupportInfo.headroom.isGpuSupported) {
throw new UnsupportedOperationException();
}
- return mGpuHeadroomIntervalMillis;
+ return mSupportInfo.headroom.gpuMinIntervalMillis;
+ }
+
+ @Override
+ public void passSessionManagerBinder(IBinder sessionManager) {
+ // Ensure caller is internal
+ if (Process.myUid() != Binder.getCallingUid()) {
+ return;
+ }
+ mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
}
@Override
@@ -1572,7 +1568,7 @@ public final class HintManagerService extends SystemService {
}
pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT);
- pw.println("HAL Support: " + isHalSupported());
+ pw.println("HAL Support: " + isHintSessionSupported());
pw.println("Active Sessions:");
synchronized (mLock) {
for (int i = 0; i < mActiveSessions.size(); i++) {
@@ -1588,20 +1584,13 @@ public final class HintManagerService extends SystemService {
}
}
}
- pw.println("CPU Headroom Interval: " + mCpuHeadroomIntervalMillis);
- pw.println("GPU Headroom Interval: " + mGpuHeadroomIntervalMillis);
+ pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis);
+ pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis);
try {
CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
- params.selectionType = CpuHeadroomParams.SelectionType.ALL;
params.usesDeviceHeadroom = true;
CpuHeadroomResult ret = getCpuHeadroom(params);
pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
- params = new CpuHeadroomParamsInternal();
- params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
- params.usesDeviceHeadroom = true;
- ret = getCpuHeadroom(params);
- pw.println("CPU headroom per core: " + (ret == null ? "N/A"
- : Arrays.toString(ret.getPerCoreHeadroom())));
} catch (Exception e) {
Slog.d(TAG, "Failed to dump CPU headroom", e);
pw.println("CPU headroom: N/A");
@@ -1688,6 +1677,8 @@ public final class HintManagerService extends SystemService {
protected boolean mHasBeenPowerEfficient;
protected boolean mHasBeenGraphicsPipeline;
protected boolean mShouldForcePause;
+ protected Integer mSessionId;
+ protected boolean mTrackedBySF;
enum SessionModes {
POWER_EFFICIENCY,
@@ -1696,7 +1687,7 @@ public final class HintManagerService extends SystemService {
protected AppHintSession(
int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
- long halSessionPtr, long durationNanos) {
+ long halSessionPtr, long durationNanos, Integer sessionId) {
mUid = uid;
mPid = pid;
mTag = sessionTag;
@@ -1710,6 +1701,8 @@ public final class HintManagerService extends SystemService {
mHasBeenPowerEfficient = false;
mHasBeenGraphicsPipeline = false;
mShouldForcePause = false;
+ mSessionId = sessionId;
+ mTrackedBySF = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowedByProcState(allowed);
try {
@@ -1799,6 +1792,19 @@ public final class HintManagerService extends SystemService {
} catch (NoSuchElementException ignored) {
Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
}
+ if (mTrackedBySF) {
+ if (mSessionManager != null) {
+ try {
+ mSessionManager.trackedSessionsDied(new int[]{mSessionId});
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ mTrackedBySF = false;
+ } else {
+ Slog.e(TAG, "SessionManager is null but there are tracked sessions");
+ }
+ }
}
synchronized (mLock) {
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
@@ -1875,6 +1881,24 @@ public final class HintManagerService extends SystemService {
}
}
+ @Override
+ public void associateToLayers(IBinder[] layerTokens) {
+ synchronized (this) {
+ if (mSessionManager != null && mSessionId != null && layerTokens != null) {
+ // Sf only untracks a session when it dies
+ if (layerTokens.length > 0) {
+ mTrackedBySF = true;
+ }
+ try {
+ mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(
+ "Could not communicate with SessionManager", e);
+ }
+ }
+ }
+ }
+
public void setThreads(@NonNull int[] tids) {
setThreadsInternal(tids, true);
}
@@ -2124,10 +2148,27 @@ public final class HintManagerService extends SystemService {
return mUid;
}
+ public boolean isTrackedBySf() {
+ synchronized (this) {
+ return mTrackedBySF;
+ }
+ }
+
+ public void setTrackedBySf(boolean tracked) {
+ synchronized (this) {
+ mTrackedBySF = tracked;
+ }
+ }
+
+
public int getTag() {
return mTag;
}
+ public Integer getSessionId() {
+ return mSessionId;
+ }
+
public long getTargetDurationNs() {
synchronized (this) {
return mTargetDurationNanos;
diff --git a/services/core/java/com/android/server/power/hint/adpf_flags.aconfig b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
index 147d76bda477..97d34836c70c 100644
--- a/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
@@ -5,3 +5,11 @@
package: "android.adpf"
container: "system"
+flag {
+ name: "adpf_viewrootimpl_action_down_boost"
+ is_exported: true
+ namespace: "game"
+ description: "Guards boosting on touch in ViewRootImpl."
+ is_fixed_read_only: true
+ bug: "360345939"
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 028ac57fc5a3..6f1810711b3a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -409,11 +409,10 @@ public class BatteryStatsImpl extends BatteryStats {
@Override
public long getWakelockDurationMillis() {
synchronized (BatteryStatsImpl.this) {
- long rawRealtimeUs = mClock.uptimeMillis() * 1000;
- long batteryUptimeUs = getBatteryUptime(rawRealtimeUs);
- long screenOnTimeUs = getScreenOnTime(rawRealtimeUs,
+ long batteryUptimeUs = getBatteryUptime(mClock.uptimeMillis() * 1000);
+ long screenOnTimeUs = getScreenOnTime(mClock.elapsedRealtime() * 1000,
BatteryStats.STATS_SINCE_CHARGED);
- return (batteryUptimeUs - screenOnTimeUs) / 1000;
+ return Math.max(0, (batteryUptimeUs - screenOnTimeUs) / 1000);
}
}
@@ -437,8 +436,9 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- if (wakeLockTimeUs != 0) {
- callback.onUidWakelockDuration(u.getUid(), wakeLockTimeUs / 1000);
+ long wakelockTimeMs = wakeLockTimeUs / 1000;
+ if (wakelockTimeMs != 0) {
+ callback.onUidWakelockDuration(u.getUid(), wakelockTimeMs);
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 63e8d9973237..8c588b4c9b98 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -195,23 +195,22 @@ public class BatteryUsageStatsProvider {
mLastAccumulationMonotonicHistorySize = historySize;
}
- handler.post(() -> accumulateBatteryUsageStats(stats));
+ // No need to store the accumulated stats asynchronously, as the entire accumulation
+ // operation is async
+ handler.post(() -> accumulateBatteryUsageStats(stats, false));
}
/**
* Computes BatteryUsageStats for the period since the last accumulated stats were stored,
- * adds them to the accumulated stats and saves the result.
+ * adds them to the accumulated stats and asynchronously saves the result.
*/
public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
- AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+ accumulateBatteryUsageStats(stats, true);
+ }
- final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includePowerStateData()
- .includeScreenStateData()
- .build();
- updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+ private void accumulateBatteryUsageStats(BatteryStatsImpl stats, boolean storeAsync) {
+ AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
powerStatsSpan.addSection(
@@ -220,8 +219,13 @@ public class BatteryUsageStatsProvider {
accumulatedStats.startWallClockTime,
accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
mMonotonicClock.write();
- mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
- accumulatedStats.builder::discard);
+ if (storeAsync) {
+ mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
+ accumulatedStats.builder::discard);
+ } else {
+ mPowerStatsStore.storePowerStatsSpan(powerStatsSpan);
+ accumulatedStats.builder.discard();
+ }
}
/**
@@ -269,7 +273,7 @@ public class BatteryUsageStatsProvider {
BatteryUsageStats batteryUsageStats;
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
- batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+ batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query);
} else if (query.getAggregatedToTimestamp() == 0) {
BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
query.getMonotonicStartTime(),
@@ -288,9 +292,13 @@ public class BatteryUsageStatsProvider {
}
private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
- BatteryUsageStatsQuery query, long currentTimeMs) {
+ BatteryUsageStatsQuery query) {
AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
- updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+ if (accumulatedStats.endMonotonicTime == MonotonicClock.UNDEFINED
+ || mMonotonicClock.monotonicTime() - accumulatedStats.endMonotonicTime
+ > query.getMaxStatsAge()) {
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
+ }
return accumulatedStats.builder.build();
}
@@ -321,7 +329,7 @@ public class BatteryUsageStatsProvider {
}
private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
- BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
+ BatteryStatsImpl stats) {
long startMonotonicTime = accumulatedStats.endMonotonicTime;
if (startMonotonicTime == MonotonicClock.UNDEFINED) {
startMonotonicTime = stats.getMonotonicStartTime();
@@ -333,6 +341,7 @@ public class BatteryUsageStatsProvider {
accumulatedStats.builder = new BatteryUsageStats.Builder(
stats.getCustomEnergyConsumerNames(), true, true, true, 0);
accumulatedStats.startWallClockTime = stats.getStartClockTime();
+ accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime();
accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
}
@@ -342,7 +351,7 @@ public class BatteryUsageStatsProvider {
accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(),
- startMonotonicTime, MonotonicClock.UNDEFINED);
+ startMonotonicTime, endMonotonicTime);
populateGeneralInfo(accumulatedStats.builder, stats);
}
diff --git a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
index e36c9946531e..e3e4e1b28f43 100644
--- a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
@@ -108,14 +108,16 @@ class WakelockPowerStatsCollector extends PowerStatsCollector {
mWakelockDurationRetriever.retrieveUidWakelockDuration((uid, durationMs) -> {
if (!mFirstCollection) {
- long[] uidStats = mPowerStats.uidStats.get(uid);
- if (uidStats == null) {
- uidStats = new long[mDescriptor.uidStatsArrayLength];
- mPowerStats.uidStats.put(uid, uidStats);
+ long diffMs = Math.max(0, durationMs - mLastUidWakelockDurations.get(uid));
+ if (diffMs != 0) {
+ long[] uidStats = mPowerStats.uidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new long[mDescriptor.uidStatsArrayLength];
+ mPowerStats.uidStats.put(uid, uidStats);
+ }
+
+ mStatsLayout.setUidUsageDuration(uidStats, diffMs);
}
-
- mStatsLayout.setUidUsageDuration(uidStats,
- Math.max(0, durationMs - mLastUidWakelockDurations.get(uid)));
}
mLastUidWakelockDurations.put(uid, durationMs);
});
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index ce6f57fec0a7..5e048810cc97 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -29,6 +29,7 @@ flag {
namespace: "backstage_power"
description: "Feature flag for streamlined connectivity battery stats"
bug: "323970018"
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
index dcdd3bd8b3fa..2609cf7aebfc 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
@@ -23,6 +23,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import java.util.function.Consumer;
@@ -169,6 +170,9 @@ public class PowerStatsAggregator {
}
}
}
+ if (endTimeMs != MonotonicClock.UNDEFINED) {
+ lastTime = endTimeMs;
+ }
if (lastTime > baseTime) {
mStats.setDuration(lastTime - baseTime);
mStats.finish(lastTime);
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e753ce845ddc..1bed48a09d9e 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -163,6 +163,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
private final Runnable mRunExpiration = this::runExpiration;
+ private final PackageWatchdog mPackageWatchdog;
// The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
// Used by #blockRollbackManager to test timeout in enabling rollbacks.
@@ -190,6 +191,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
+ mPackageWatchdog = PackageWatchdog.getInstance(mContext);
// Kick off and start monitoring the handler thread.
HandlerThread handlerThread = new HandlerThread("RollbackManagerServiceHandler");
@@ -1249,12 +1251,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
// should document in PackageInstaller.SessionParams#setEnableRollback
// After enabling and committing any rollback, observe packages and
// prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
} else {
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
runExpiration();
}
@@ -1317,7 +1319,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
}
});
- PackageWatchdog.getInstance(mContext).dump(ipw);
+ mPackageWatchdog.dump(ipw);
}
@AnyThread
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index 2cc08c327b71..7fb57085fb35 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -80,13 +80,25 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
private void initFeatures(boolean enabled) {
if (android.security.Flags.aapmFeatureDisableInstallUnknownSources()) {
+ try {
mHooks.add(new DisallowInstallUnknownSourcesAdvancedProtectionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize DisallowInstallUnknownSources", e);
+ }
}
if (android.security.Flags.aapmFeatureMemoryTaggingExtension()) {
+ try {
mHooks.add(new MemoryTaggingExtensionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize MemoryTaggingExtension", e);
+ }
}
if (android.security.Flags.aapmFeatureDisableCellular2g()) {
+ try {
mHooks.add(new DisallowCellular2GAdvancedProtectionHook(mContext, enabled));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to initialize DisallowCellular2g", e);
+ }
}
}
@@ -199,7 +211,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
}
void sendCallbackAdded(boolean enabled, IAdvancedProtectionCallback callback) {
- Message.obtain(mHandler, MODE_CHANGED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1,
+ Message.obtain(mHandler, CALLBACK_ADDED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1,
/*callback*/ callback)
.sendToTarget();
}
@@ -278,8 +290,13 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
for (int i = 0; i < mHooks.size(); i++) {
AdvancedProtectionHook feature = mHooks.get(i);
- if (feature.isAvailable()) {
- feature.onAdvancedProtectionChanged(enabled);
+ try {
+ if (feature.isAvailable()) {
+ feature.onAdvancedProtectionChanged(enabled);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to call hook for feature "
+ + feature.getFeature().getId(), e);
}
}
synchronized (mCallbacks) {
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
index b9c8d3dc5319..f51c25d6761c 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -24,9 +24,14 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserManager;
import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
/** @hide */
public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
private static final String TAG = "AdvancedProtectionDisallowCellular2G";
@@ -35,11 +40,13 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
private final DevicePolicyManager mDevicePolicyManager;
private final TelephonyManager mTelephonyManager;
+ private final SubscriptionManager mSubscriptionManager;
public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
super(context, enabled);
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
setPolicy(enabled);
}
@@ -50,14 +57,44 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
return mFeature;
}
+ private static boolean isEmbeddedSubscriptionVisible(SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded()
+ && (subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING
+ || (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()
+ && subInfo.isOnlyNonTerrestrialNetwork()))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private List<TelephonyManager> getActiveTelephonyManagers() {
+ List<TelephonyManager> telephonyManagers = new ArrayList<>();
+
+ for (SubscriptionInfo subInfo : mSubscriptionManager.getActiveSubscriptionInfoList()) {
+ if (isEmbeddedSubscriptionVisible(subInfo)) {
+ telephonyManagers.add(
+ mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()));
+ }
+ }
+
+ return telephonyManagers;
+ }
+
@Override
public boolean isAvailable() {
- return mTelephonyManager.isDataCapable();
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ if (telephonyManager.isDataCapable()
+ && telephonyManager.isRadioInterfaceCapabilitySupported(
+ mTelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)) {
+ return true;
+ }
+ }
+
+ return false;
}
private void setPolicy(boolean enabled) {
- Slog.i(TAG, "setPolicy called with " + enabled);
-
if (enabled) {
Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
mDevicePolicyManager.addUserRestrictionGlobally(
@@ -75,12 +112,14 @@ public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProt
// Leave 2G disabled even if APM is disabled.
if (!enabled) {
- long oldAllowedTypes =
- mTelephonyManager.getAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
- long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
- mTelephonyManager.setAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ long oldAllowedTypes =
+ telephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ telephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
index 21752e524619..bb523d63c43a 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
@@ -19,13 +19,24 @@ package com.android.server.security.advancedprotection.features;
import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
+import android.Manifest;
import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.RemoteException;
import android.os.UserManager;
import android.security.advancedprotection.AdvancedProtectionFeature;
import android.util.Slog;
+import com.android.server.LocalServices;
+
/** @hide */
public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
extends AdvancedProtectionHook {
@@ -33,13 +44,25 @@ public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
+
+ private final ActivityManagerInternal mActivityManagerInternal;
+ private final AppOpsManager mAppOpsManager;
private final DevicePolicyManager mDevicePolicyManager;
+ private final IPackageManager mIPackageManager;
+ private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
public DisallowInstallUnknownSourcesAdvancedProtectionHook(@NonNull Context context,
boolean enabled) {
super(context, enabled);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
- onAdvancedProtectionChanged(enabled);
+ mIPackageManager = AppGlobals.getPackageManager();
+ mUserManager = context.getSystemService(UserManager.class);
+ mPackageManager = context.getPackageManager();
+
+ setRestriction(enabled);
}
@NonNull
@@ -53,21 +76,47 @@ public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
return true;
}
- @Override
- public void onAdvancedProtectionChanged(boolean enabled) {
+ private void setRestriction(boolean enabled) {
if (enabled) {
Slog.d(TAG, "Setting DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
mDevicePolicyManager.addUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
- return;
+ } else {
+ Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+ mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
}
- Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
- mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+ }
- // TODO(b/369361373):
- // 1. After clearing the restriction, set AppOpsManager.OP_REQUEST_INSTALL_PACKAGES to
- // disabled.
- // 2. Update dialog strings.
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ setRestriction(enabled);
+ if (enabled) return;
+
+ // Leave OP_REQUEST_INSTALL_PACKAGES disabled when APM is disabled.
+ Slog.d(TAG, "Setting all OP_REQUEST_INSTALL_PACKAGES to MODE_ERRORED");
+ for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+ try {
+ final String[] packagesWithRequestInstallPermission = mIPackageManager
+ .getAppOpPermissionPackages(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES, userInfo.id);
+ for (String packageName : packagesWithRequestInstallPermission) {
+ try {
+ int uid = mPackageManager.getPackageUidAsUser(packageName, userInfo.id);
+ boolean isCallerInstrumented = mActivityManagerInternal
+ .getInstrumentationSourceUid(uid) != Process.INVALID_UID;
+ if (!isCallerInstrumented) {
+ mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid,
+ packageName, AppOpsManager.MODE_ERRORED);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Couldn't retrieve uid for a package: " + e);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't retrieve packages with REQUEST_INSTALL_PACKAGES."
+ + " getAppOpPermissionPackages() threw the following exception: " + e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
index 0ea88e8523f0..687442b47fb3 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -28,6 +28,7 @@ import com.android.server.ServiceThread;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
public class DataAggregator {
private static final String TAG = "IntrusionDetection DataAggregator";
@@ -36,11 +37,10 @@ public class DataAggregator {
private static final int MSG_DISABLE = 2;
private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
- private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER =
- new IntrusionDetectionAdminReceiver();
private final IntrusionDetectionService mIntrusionDetectionService;
private final ArrayList<DataSource> mDataSources;
+ private final AtomicBoolean mIsLoggingInitialized = new AtomicBoolean(false);
private Context mContext;
private List<IntrusionDetectionEvent> mStoredEvents = new ArrayList<>();
@@ -59,30 +59,20 @@ public class DataAggregator {
mHandler = new EventHandler(looper, this);
}
- /**
- * Initialize DataSources
- * @return Whether the initialization succeeds.
- */
- public boolean initialize() {
- SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this);
- mDataSources.add(securityLogSource);
-
- NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this);
- ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource);
- mDataSources.add(networkLogSource);
-
- for (DataSource ds : mDataSources) {
- if (!ds.initialize()) {
- return false;
- }
- }
- return true;
+ /** Initialize DataSources */
+ private void initialize() {
+ mDataSources.add(new SecurityLogSource(mContext, this));
+ mDataSources.add(new NetworkLogSource(mContext, this));
}
/**
* Enable the data collection of all DataSources.
*/
public void enable() {
+ if (!mIsLoggingInitialized.get()) {
+ initialize();
+ mIsLoggingInitialized.set(true);
+ }
mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
/* allowIo */ false);
mHandlerThread.start();
@@ -111,9 +101,6 @@ public class DataAggregator {
*/
public void disable() {
mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
- for (DataSource ds : mDataSources) {
- ds.disable();
- }
}
private void onNewSingleData(IntrusionDetectionEvent event) {
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
index 61fac46be82d..0bc448245b76 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
@@ -18,11 +18,6 @@ package com.android.server.security.intrusiondetection;
public interface DataSource {
/**
- * Initialize the data source.
- */
- boolean initialize();
-
- /**
* Enable the data collection.
*/
void enable();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
deleted file mode 100644
index dba7374fe02a..000000000000
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
+++ /dev/null
@@ -1,42 +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.security.intrusiondetection;
-
-import android.app.admin.DeviceAdminReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Slog;
-
-public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver {
- private static final String TAG = "IntrusionDetectionAdminReceiver";
-
- private static NetworkLogSource sNetworkLogSource;
-
- @Override
- public void onNetworkLogsAvailable(
- Context context, Intent intent, long batchToken, int networkLogsCount) {
- if (sNetworkLogSource != null) {
- sNetworkLogSource.onNetworkLogsAvailable(batchToken);
- } else {
- Slog.w(TAG, "Network log receiver is not initialized");
- }
- }
-
- public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) {
- sNetworkLogSource = networkLogSource;
- }
-}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
index b25656ebf47f..a16e66de09a9 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -24,42 +24,56 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.infra.AndroidFuture;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Process;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
public class IntrusionDetectionEventTransportConnection implements ServiceConnection {
+ private static final String PRODUCTION_BUILD = "user";
+ private static final String PROPERTY_BUILD_TYPE = "ro.build.type";
+ private static final String PROPERTY_INTRUSION_DETECTION_SERVICE_NAME =
+ "debug.intrusiondetection_package_name";
+ private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min
private static final String TAG = "IntrusionDetectionEventTransportConnection";
- private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
private final Context mContext;
private String mIntrusionDetectionEventTransportConfig;
volatile IIntrusionDetectionEventTransport mService;
+
public IntrusionDetectionEventTransportConnection(Context context) {
mContext = context;
- mService = null;
}
/**
* Initialize the IntrusionDetectionEventTransport binder service.
- * @return Whether the initialization succeed.
+ *
+ * @return Whether the initialization succeeds.
*/
public boolean initialize() {
+ Slog.d(TAG, "initialize");
if (!bindService()) {
return false;
}
+ // Wait for the service to be connected before calling initialize.
+ waitForConnection();
AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
try {
mService.initialize(resultFuture);
@@ -77,6 +91,20 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec
}
}
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ Slog.d(TAG, "waiting for connection to service...");
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ /* never interrupted */
+ }
+ }
+ Slog.d(TAG, "connected to service");
+ }
+ }
+
/**
* Add data to the IntrusionDetectionEventTransport binder service.
* @param data List of IntrusionDetectionEvent.
@@ -118,11 +146,42 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec
}
}
+ private String getSystemPropertyValue(String propertyName) {
+ String commandString = "getprop " + propertyName;
+ try {
+ Process process = Runtime.getRuntime().exec(commandString);
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String propertyValue = reader.readLine();
+ reader.close();
+ return propertyValue;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get system property value:", e);
+ return null;
+ }
+ }
+
private boolean bindService() {
- mIntrusionDetectionEventTransportConfig = mContext.getString(
- com.android.internal.R.string.config_intrusionDetectionEventTransport);
+ String buildType = getSystemPropertyValue(PROPERTY_BUILD_TYPE);
+ mIntrusionDetectionEventTransportConfig =
+ mContext.getString(
+ com.android.internal.R.string.config_intrusionDetectionEventTransport);
+
+ // If the build type is not production, and a property value is set, use it instead.
+ // This allows us to test the service with a different config.
+ if (!buildType.equals(PRODUCTION_BUILD)
+ && !TextUtils.isEmpty(
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME))) {
+ mIntrusionDetectionEventTransportConfig =
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME);
+ }
+ Slog.d(
+ TAG,
+ "mIntrusionDetectionEventTransportConfig: "
+ + mIntrusionDetectionEventTransportConfig);
+
if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) {
- Slog.e(TAG, "config_intrusionDetectionEventTransport is empty");
+ Slog.e(TAG, "Unable to find a valid config for the transport service");
return false;
}
@@ -163,11 +222,19 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ synchronized (this) {
+ mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ Slog.d(TAG, "connected to service");
+ this.notifyAll();
+ }
}
@Override
public void onServiceDisconnected(ComponentName name) {
- mService = null;
+ synchronized (this) {
+ mService = null;
+ Slog.d(TAG, "disconnected from service");
+ this.notifyAll();
+ }
}
}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
index 0287b415b9c2..8ff1c7f22ffb 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java
@@ -232,12 +232,10 @@ public class IntrusionDetectionService extends SystemService {
return;
}
- // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest.
- // Enable it when the transport component is ready.
- // if (!mIntrusionDetectionEventTransportConnection.initialize()) {
- // callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
- // return;
- // }
+ if (!mIntrusionDetectionEventTransportConnection.initialize()) {
+ callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE);
+ return;
+ }
mDataAggregator.enable();
mState = STATE_ENABLED;
@@ -252,9 +250,7 @@ public class IntrusionDetectionService extends SystemService {
return;
}
- // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest.
- // Enable it when the transport component is ready.
- // mIntrusionDetectionEventTransportConnection.release();
+ mIntrusionDetectionEventTransportConnection.release();
mDataAggregator.disable();
mState = STATE_DISABLED;
notifyStateMonitors();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
index 1c93d3f9c6a1..083b1fd61c46 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -17,118 +17,131 @@
package com.android.server.security.intrusiondetection;
import android.app.admin.ConnectEvent;
-import android.app.admin.DevicePolicyManager;
import android.app.admin.DnsEvent;
-import android.app.admin.NetworkEvent;
-import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.util.Slog;
-import java.util.List;
-import java.util.stream.Collectors;
+import com.android.server.LocalServices;
+import com.android.server.net.BaseNetdEventCallback;
+
+import java.util.concurrent.atomic.AtomicBoolean;
public class NetworkLogSource implements DataSource {
private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
+ private final AtomicBoolean mIsNetworkLoggingEnabled = new AtomicBoolean(false);
+ private final PackageManagerInternal mPm;
- private DevicePolicyManager mDpm;
- private ComponentName mAdmin;
private DataAggregator mDataAggregator;
- public NetworkLogSource(Context context, DataAggregator dataAggregator) {
+ private IIpConnectivityMetrics mIpConnectivityMetrics;
+ private long mId;
+
+ public NetworkLogSource(Context context, DataAggregator dataAggregator)
+ throws SecurityException {
mDataAggregator = dataAggregator;
- mDpm = context.getSystemService(DevicePolicyManager.class);
- mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class);
+ mPm = LocalServices.getService(PackageManagerInternal.class);
+ mId = 0;
+ initIpConnectivityMetrics();
}
- @Override
- public boolean initialize() {
- try {
- if (!mDpm.isAdminActive(mAdmin)) {
- Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin");
- return false;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "Security exception in initialize: ", e);
- return false;
- }
- return true;
+ private void initIpConnectivityMetrics() {
+ mIpConnectivityMetrics =
+ (IIpConnectivityMetrics)
+ IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
}
@Override
public void enable() {
- enableNetworkLog();
- }
-
- @Override
- public void disable() {
- disableNetworkLog();
- }
-
- private void enableNetworkLog() {
- if (!isNetworkLogEnabled()) {
- mDpm.setNetworkLoggingEnabled(mAdmin, true);
+ if (mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already enabled");
+ return;
}
- }
-
- private void disableNetworkLog() {
- if (isNetworkLogEnabled()) {
- mDpm.setNetworkLoggingEnabled(mAdmin, false);
+ try {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
+ mIsNetworkLoggingEnabled.set(true);
+ } else {
+ Slog.e(TAG, "Failed to enable network logging; invalid callback");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to enable network logging; ", e);
}
}
- private boolean isNetworkLogEnabled() {
- return mDpm.isNetworkLoggingEnabled(mAdmin);
- }
-
- /**
- * Retrieve network logs when onNetworkLogsAvailable callback is received.
- *
- * @param batchToken The token representing the current batch of network logs.
- */
- public void onNetworkLogsAvailable(long batchToken) {
- List<NetworkEvent> events;
- try {
- events = mDpm.retrieveNetworkLogs(mAdmin, batchToken);
- } catch (SecurityException e) {
- Slog.e(
- TAG,
- "Admin "
- + mAdmin.flattenToString()
- + "does not have permission to retrieve network logs",
- e);
+ @Override
+ public void disable() {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already disabled");
return;
}
- if (events == null) {
- if (!isNetworkLogEnabled()) {
- Slog.w(TAG, "Network logging is disabled");
+ try {
+ if (!mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST)) {
+
+ mIsNetworkLoggingEnabled.set(false);
} else {
- Slog.e(TAG, "Invalid batch token: " + batchToken);
+ Slog.e(TAG, "Failed to enable network logging; invalid callback");
}
- return;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to disable network logging; ", e);
}
-
- List<IntrusionDetectionEvent> intrusionDetectionEvents =
- events.stream()
- .filter(event -> event != null)
- .map(event -> toIntrusionDetectionEvent(event))
- .collect(Collectors.toList());
- mDataAggregator.addBatchData(intrusionDetectionEvents);
}
- private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) {
- if (event instanceof DnsEvent) {
- DnsEvent dnsEvent = (DnsEvent) event;
- return new IntrusionDetectionEvent(dnsEvent);
- } else if (event instanceof ConnectEvent) {
- ConnectEvent connectEvent = (ConnectEvent) event;
- return new IntrusionDetectionEvent(connectEvent);
+ private void incrementEventID() {
+ if (mId == Long.MAX_VALUE) {
+ Slog.i(TAG, "Reached maximum id value; wrapping around.");
+ mId = 0;
+ } else {
+ mId++;
}
- throw new IllegalArgumentException(
- "Invalid event type with ID: "
- + event.getId()
- + "from package: "
- + event.getPackageName());
}
+
+ private final INetdEventCallback mNetdEventCallback =
+ new BaseNetdEventCallback() {
+ @Override
+ public void onDnsEvent(
+ int netId,
+ int eventType,
+ int returnCode,
+ String hostname,
+ String[] ipAddresses,
+ int ipAddressesCount,
+ long timestamp,
+ int uid) {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ return;
+ }
+ DnsEvent dnsEvent =
+ new DnsEvent(
+ hostname,
+ ipAddresses,
+ ipAddressesCount,
+ mPm.getNameForUid(uid),
+ timestamp);
+ dnsEvent.setId(mId);
+ incrementEventID();
+ mDataAggregator.addSingleData(new IntrusionDetectionEvent(dnsEvent));
+ }
+
+ @Override
+ public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+ if (!mIsNetworkLoggingEnabled.get()) {
+ return;
+ }
+ ConnectEvent connectEvent =
+ new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp);
+ connectEvent.setId(mId);
+ incrementEventID();
+ mDataAggregator.addSingleData(new IntrusionDetectionEvent(connectEvent));
+ }
+ };
}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
index c5f736e383b2..5611905bf270 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -43,26 +43,9 @@ public class SecurityLogSource implements DataSource {
mDataAggregator = dataAggregator;
mDpm = context.getSystemService(DevicePolicyManager.class);
mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public boolean initialize() {
- // Confirm caller is system and the device is managed. Otherwise logs will
- // be redacted.
- try {
- if (!mDpm.isDeviceManaged()) {
- Slog.e(TAG, "Caller does not have device owner permissions");
- return false;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "Security exception in initialize: ", e);
- return false;
- }
mEventCallback = new SecurityEventCallback();
- return true;
}
-
@Override
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void enable() {
@@ -99,6 +82,10 @@ public class SecurityLogSource implements DataSource {
@Override
public void accept(List<SecurityEvent> events) {
+ if (events.size() == 0) {
+ Slog.w(TAG, "No events received; caller may not be authorized");
+ return;
+ }
List<IntrusionDetectionEvent> intrusionDetectionEvents =
events.stream()
.filter(event -> event != null)
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 908f51b9cba9..f8877ad912b5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -129,6 +129,8 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -339,7 +341,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onDisplayAdded(int displayId) {}
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
+ }
@Override
public void onDisplayRemoved(int displayId) {
@@ -1710,8 +1716,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
icons = new ArrayMap<>(mIcons);
}
synchronized (mLock) {
- // TODO(b/118592525): Currently, status bar only works on the default display.
- // Make it aware of multi-display if needed.
final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY);
return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
@@ -1722,6 +1726,46 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
+ @Override
+ public Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar bar) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+
+ Slog.i(TAG, "registerStatusBarForAllDisplays bar=" + bar);
+ mBar = bar;
+ mDeathRecipient.linkToDeath();
+ notifyBarAttachChanged();
+
+ synchronized (mLock) {
+ Map<String, RegisterStatusBarResult> results = new HashMap<>();
+
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ final int displayId = mDisplayUiState.keyAt(i);
+ final UiState state = mDisplayUiState.get(displayId);
+
+ final ArrayMap<String, StatusBarIcon> icons;
+ synchronized (mIcons) {
+ icons = new ArrayMap<>(mIcons);
+ }
+
+ if (state != null) {
+ results.put(String.valueOf(displayId),
+ new RegisterStatusBarResult(icons,
+ gatherDisableActionsLocked(mCurrentUserId, 1),
+ state.mAppearance, state.mAppearanceRegions,
+ state.mImeWindowVis,
+ state.mImeBackDisposition, state.mShowImeSwitcher,
+ gatherDisableActionsLocked(mCurrentUserId, 2),
+ state.mNavbarColorManagedByIme, state.mBehavior,
+ state.mRequestedVisibleTypes,
+ state.mPackageName, state.mTransientBarTypes,
+ state.mLetterboxDetails));
+ }
+ }
+ return results;
+ }
+ }
+
private void notifyBarAttachChanged() {
UiThread.getHandler().post(() -> {
if (mGlobalActionListener == null) return;
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index 63a3e5ae7d8b..a38fc5bb6e45 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.PackageManagerInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -63,7 +64,10 @@ public class TelecomLoaderService extends SystemService {
// as this loader (process="system") that's redundant here.
try {
ITelecomLoader telecomLoader = ITelecomLoader.Stub.asInterface(service);
- ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo);
+ PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo,
+ packageManagerInternal.getSystemUiServiceComponent().getPackageName());
SmsApplication.getDefaultMmsApplication(mContext, false);
ServiceManager.addService(Context.TELECOM_SERVICE, telecomService.asBinder());
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 38bc026c473a..e191ff20a518 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -119,14 +119,14 @@ public final class ClientProfile {
* If resource holder retains ownership of the resource in a challenge scenario then value is
* true.
*/
- private boolean mResourceHolderRetain;
+ private boolean mResourceOwnershipRetention;
private ClientProfile(Builder builder) {
this.mId = builder.mId;
this.mTvInputSessionId = builder.mTvInputSessionId;
this.mUseCase = builder.mUseCase;
this.mProcessId = builder.mProcessId;
- this.mResourceHolderRetain = builder.mResourceHolderRetain;
+ this.mResourceOwnershipRetention = builder.mResourceOwnershipRetention;
}
public int getId() {
@@ -149,8 +149,8 @@ public final class ClientProfile {
* Returns true when the resource holder retains ownership of the resource in a challenge
* scenario.
*/
- public boolean shouldResourceHolderRetain() {
- return mResourceHolderRetain;
+ public boolean resourceOwnershipRetentionEnabled() {
+ return mResourceOwnershipRetention;
}
/**
@@ -199,12 +199,12 @@ public final class ClientProfile {
* scenario, when both resource holder and resource challenger have same processId and same
* priority.
*
- * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
- * false (or resourceHolderRetain not set at all) to allow the resource challenger to
- * acquire the resource. If not explicitly set, resourceHolderRetain is set to false.
+ * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+ * or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, enabled is set to {@code false}.
*/
- public void setResourceHolderRetain(boolean resourceHolderRetain) {
- mResourceHolderRetain = resourceHolderRetain;
+ public void setResourceOwnershipRetention(boolean enabled) {
+ mResourceOwnershipRetention = enabled;
}
/**
@@ -389,7 +389,7 @@ public final class ClientProfile {
private String mTvInputSessionId;
private int mUseCase;
private int mProcessId;
- private boolean mResourceHolderRetain = false;
+ private boolean mResourceOwnershipRetention = false;
Builder(int id) {
this.mId = id;
@@ -428,12 +428,12 @@ public final class ClientProfile {
/**
* Builder for {@link ClientProfile}.
*
- * @param resourceHolderRetain the determining factor for resource ownership during
- * challenger scenario. The default behavior favors the resource challenger and grants
- * them ownership of the resource if resourceHolderRetain is not explicitly set to true.
+ * @param enabled the determining factor for resource ownership during challenger scenario.
+ * The default behavior favors the resource challenger and grants them ownership of
+ * the resource if resourceOwnershipRetention is not explicitly set to true.
*/
- public Builder resourceHolderRetain(boolean resourceHolderRetain) {
- this.mResourceHolderRetain = resourceHolderRetain;
+ public Builder resourceOwnershipRetention(boolean enabled) {
+ this.mResourceOwnershipRetention = enabled;
return this;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 5ae8c11f1d8f..bb192c0b4603 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -231,10 +231,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde
}
@Override
- public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
- enforceTrmAccessPermission("setResourceHolderRetain");
+ public void setResourceOwnershipRetention(int clientId, boolean enabled) {
+ enforceTrmAccessPermission("setResourceOwnershipRetention");
synchronized (mLock) {
- getClientProfile(clientId).setResourceHolderRetain(resourceHolderRetain);
+ getClientProfile(clientId).setResourceOwnershipRetention(enabled);
}
}
@@ -1079,7 +1079,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
return true;
@@ -1265,7 +1266,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
return true;
@@ -1352,7 +1354,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
casSessionHandle[0] = cas.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1439,7 +1442,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
ciCamHandle[0] = ciCam.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1677,7 +1681,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess
&& !(setResourceHolderRetain()
- && requestClient.shouldResourceHolderRetain())))) {
+ && requestClient
+ .resourceOwnershipRetentionEnabled())))) {
demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
return true;
diff --git a/services/core/java/com/android/server/vcn/Android.bp b/services/core/java/com/android/server/vcn/Android.bp
deleted file mode 100644
index ab5da3ee45b4..000000000000
--- a/services/core/java/com/android/server/vcn/Android.bp
+++ /dev/null
@@ -1,13 +0,0 @@
-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-vcn-util-sources",
- srcs: ["util/**/*.java"],
-}
diff --git a/services/core/java/com/android/server/vcn/TEST_MAPPING b/services/core/java/com/android/server/vcn/TEST_MAPPING
deleted file mode 100644
index 5b04d884fc1a..000000000000
--- a/services/core/java/com/android/server/vcn/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "FrameworksVcnTests"
- },
- {
- "name": "CtsVcnTestCases"
- }
- ]
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 9e75cf2fc3f3..1b6ce9dacfa9 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -86,6 +86,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@GuardedBy("mLock")
private Status mEndStatusRequest;
@GuardedBy("mLock")
+ private boolean mEndedByVendor;
+ @GuardedBy("mLock")
private long mStartTime; // for debugging
@GuardedBy("mLock")
private long mEndUptime;
@@ -117,16 +119,19 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void finishSession() {
+ Slog.d(TAG, "Session finish requested, ending vibration session...");
// Do not abort session in HAL, wait for ongoing vibration requests to complete.
// This might take a while to end the session, but it can be aborted by cancelSession.
- requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+ requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
}
@Override
public void cancelSession() {
+ Slog.d(TAG, "Session cancel requested, aborting vibration session...");
// Always abort session in HAL while cancelling it.
// This might be triggered after finishSession was already called.
- requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
@@ -158,7 +163,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
public DebugInfo getDebugInfo() {
synchronized (mLock) {
return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
- mEndUptime, mEndTime, mVibrations);
+ mEndUptime, mEndTime, mEndedByVendor, mVibrations);
}
}
@@ -172,13 +177,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void onCancel() {
Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
public void binderDied() {
Slog.d(TAG, "Binder died, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
+ /* isVendorRequest= */ false);
}
@Override
@@ -207,7 +214,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
// All requests to end a session should abort it to stop ongoing vibrations, even if
// immediate flag is false. Only the #finishSession API will not abort and wait for
// session vibrations to complete, which might take a long time.
- requestEndSession(status, /* shouldAbort= */ true);
+ requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
}
@Override
@@ -223,12 +230,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void notifySessionCallback() {
synchronized (mLock) {
+ Slog.d(TAG, "Session callback received, ending vibration session...");
// If end was not requested then the HAL has cancelled the session.
- maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+ maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+ /* isVendorRequest= */ false);
maybeSetStatusToRequestedLocked();
clearVibrationConductor();
+ mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
}
- mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
}
@Override
@@ -335,10 +344,10 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
}
- private void requestEndSession(Status status, boolean shouldAbort) {
+ private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
boolean shouldTriggerSessionHook = false;
synchronized (mLock) {
- maybeSetEndRequestLocked(status);
+ maybeSetEndRequestLocked(status, isVendorRequest);
if (isStarted()) {
// Always trigger session hook after it has started, in case new request aborts an
// already finishing session. Wait for HAL callback before actually ending here.
@@ -354,12 +363,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
@GuardedBy("mLock")
- private void maybeSetEndRequestLocked(Status status) {
+ private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
if (mEndStatusRequest != null) {
// End already requested, keep first requested status and time.
return;
}
mEndStatusRequest = status;
+ mEndedByVendor = isVendorRequest;
mEndTime = System.currentTimeMillis();
mEndUptime = SystemClock.uptimeMillis();
if (mConductor != null) {
@@ -442,15 +452,18 @@ final class VendorVibrationSession extends IVibrationSession.Stub
private final long mStartTime;
private final long mEndTime;
private final long mDurationMs;
+ private final boolean mEndedByVendor;
DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
- long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
+ long startTime, long endUptime, long endTime, boolean endedByVendor,
+ List<DebugInfo> vibrations) {
mStatus = status;
mCallerInfo = callerInfo;
mCreateUptime = createUptime;
mCreateTime = createTime;
mStartTime = startTime;
mEndTime = endTime;
+ mEndedByVendor = endedByVendor;
mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
}
@@ -478,6 +491,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ if (mStartTime > 0) {
+ // Only log sessions that have started.
+ statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
+ statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
+ mVibrations.size());
+ if (!mEndedByVendor) {
+ statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
+ }
+ }
for (DebugInfo vibration : mVibrations) {
vibration.logMetrics(statsLogger);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 27f92b2080e6..2bf44981e6d5 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,6 +25,7 @@ import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -211,6 +212,11 @@ abstract class Vibration {
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
statsLogger.writeVibrationReportedAsync(mStatsInfo);
+ if (Flags.vendorVibrationEffects()) {
+ // Log effect as it was originally requested.
+ statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
+ mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index e9c38940601c..08da43d8f0e0 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -16,8 +16,12 @@
package com.android.server.vibrator;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.Handler;
+import android.os.Parcel;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.util.Slog;
import android.view.HapticFeedbackConstants;
@@ -58,6 +62,16 @@ public class VibratorFrameworkStatsLogger {
"vibrator.value_vibration_adaptive_haptic_scale",
new Histogram.UniformOptions(20, 0, 2));
+ // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
+ private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_effect_size",
+ new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
+
+ // Session vibration count in [0, ~840) defined by scaled buckets.
+ private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_session_vibrations",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@ public class VibratorFrameworkStatsLogger {
Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
}
}
+
+ /** Logs when a vendor vibration session successfully started. */
+ public void logVibrationVendorSessionStarted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
+ }
+
+ /**
+ * Logs when a vendor vibration session is interrupted by the platform.
+ *
+ * <p>A vendor session is interrupted if it has successfully started and its end was not
+ * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
+ * the vibrator HAL triggering the session completed callback early.
+ */
+ public void logVibrationVendorSessionInterrupted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
+ }
+
+ /** Logs the number of vibrations requested for a single vendor vibration session. */
+ public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
+ sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
+ }
+
+ /**
+ * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
+ * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
+ */
+ public void logVibrationCountAndSizeIfVendorEffect(int uid,
+ @Nullable CombinedVibration vibration) {
+ if (vibration == null) {
+ return;
+ }
+ boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
+ if (hasVendorEffects) {
+ // Increment CombinedVibration with one or more vendor effects only once.
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
+ }
+ }
+
+ private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
+ if (vibration instanceof CombinedVibration.Mono mono) {
+ if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ return true;
+ }
+ return false;
+ }
+ if (vibration instanceof CombinedVibration.Stereo stereo) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < stereo.getEffects().size(); i++) {
+ if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ hasVendorEffects = true;
+ }
+ }
+ return hasVendorEffects;
+ }
+ if (vibration instanceof CombinedVibration.Sequential sequential) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < sequential.getEffects().size(); i++) {
+ hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
+ sequential.getEffects().get(i));
+ }
+ return hasVendorEffects;
+ }
+ // Unknown combined vibration, skip metrics.
+ return false;
+ }
+
+ private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
+ int dataSize;
+ Parcel vendorData = Parcel.obtain();
+ try {
+ // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
+ // PersistableBundle creates an XML representation for the data in writeToStream, so it
+ // might be larger than the actual data that is transferred between processes.
+ effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+ dataSize = vendorData.dataSize();
+ } finally {
+ vendorData.recycle();
+ }
+ sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cc163db4dc36..ae726c15ed79 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1197,7 +1197,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
SystemClock.uptimeMillis(), System.currentTimeMillis(),
/* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
- /* vibrations= */ null));
+ /* endedByVendor= */ false, /* vibrations= */ null));
}
private void logAndRecordVibration(DebugInfo info) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bbef5785dfcb..415896b6230f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -50,6 +50,7 @@ import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -2487,7 +2488,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
if (liveWallpaperContentHandling()) {
- return getWallpaperInstance(which, userId, false).getInfo();
+ WallpaperInstance instance = getWallpaperInstance(which, userId, false);
+ return (instance != null) ? instance.getInfo() : null;
}
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -2509,7 +2511,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return null;
}
- @NonNull
+ @Nullable
@Override
public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId) {
return getWallpaperInstance(which, userId, true);
@@ -2517,28 +2519,27 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId,
boolean requireReadWallpaper) {
- final WallpaperInstance defaultInstance = new WallpaperInstance(null,
- new WallpaperDescription.Builder().build());
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
synchronized (mLock) {
WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
: mWallpaperMap.get(userId);
- if (wallpaper == null
- || wallpaper.connection == null
- || wallpaper.connection.mInfo == null) {
- return defaultInstance;
- }
+ if (wallpaper == null || wallpaper.connection == null) return null;
WallpaperInfo info = wallpaper.connection.mInfo;
- boolean canQueryPackage = mPackageManagerInternal.canQueryPackage(
+ boolean canQueryPackage = (info == null) || mPackageManagerInternal.canQueryPackage(
Binder.getCallingUid(), info.getComponent().getPackageName());
if (hasPermission(READ_WALLPAPER_INTERNAL)
|| (canQueryPackage && !requireReadWallpaper)) {
- return new WallpaperInstance(info, wallpaper.getDescription());
+ // TODO(b/380245309) Remove this when crops are part of the description.
+ WallpaperDescription description =
+ wallpaper.getDescription().toBuilder().setCropHints(
+ wallpaper.mCropHints).build();
+ return new WallpaperInstance(info, description);
+ } else {
+ return null;
}
}
- return defaultInstance;
}
@Override
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 92ce251c4293..179866153b5a 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -34,6 +34,7 @@ import android.util.Log;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewZygote;
@@ -246,6 +247,11 @@ public class SystemImpl implements SystemInterface {
}
@Override
+ public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+ return WebViewFactoryProvider.isCompatibleImplementationPackage(packageInfo);
+ }
+
+ @Override
public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
}
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 67105542e6ef..d9e1a3a5e02d 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -47,6 +47,9 @@ public interface SystemInterface {
boolean systemIsDebuggable();
PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
+ /** Check if the given package is a compatible WebView implementation for the OS. */
+ boolean isCompatibleImplementationPackage(PackageInfo packageInfo);
+
/**
* Get the PackageInfos of all users for the package represented by {@param configInfo}.
* @return an array of UserPackages for a certain package, each UserPackage being belonging to a
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index a5a02cdedf97..9e8dc2690a9d 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -27,6 +27,7 @@ import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -79,7 +80,7 @@ class WebViewUpdateServiceImpl2 {
private static final long NS_PER_MS = 1000000;
private static final int VALIDITY_OK = 0;
- private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+ private static final int VALIDITY_OS_INCOMPATIBLE = 1;
private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
@@ -587,9 +588,9 @@ class WebViewUpdateServiceImpl2 {
}
private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
- // Ensure the provider targets this framework release (or a later one).
- if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
- return VALIDITY_INCORRECT_SDK_VERSION;
+ // Ensure the provider is compatible with this framework release.
+ if (!mSystemInterface.isCompatibleImplementationPackage(packageInfo)) {
+ return VALIDITY_OS_INCOMPATIBLE;
}
if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
&& !mSystemInterface.systemIsDebuggable()) {
@@ -712,7 +713,8 @@ class WebViewUpdateServiceImpl2 {
}
pw.println(
TextUtils.formatSimple(
- " Minimum targetSdkVersion: %d", UserPackage.MINIMUM_SUPPORTED_SDK));
+ " %s",
+ WebViewFactoryProvider.describeCompatibleImplementationPackage()));
pw.println(
TextUtils.formatSimple(
" Minimum WebView version code: %d", mMinimumVersionCode));
@@ -786,8 +788,8 @@ class WebViewUpdateServiceImpl2 {
private static String getInvalidityReason(int invalidityReason) {
switch (invalidityReason) {
- case VALIDITY_INCORRECT_SDK_VERSION:
- return "SDK version too low";
+ case VALIDITY_OS_INCOMPATIBLE:
+ return "Not compatible with this OS version";
case VALIDITY_INCORRECT_VERSION_CODE:
return "Version code too low";
case VALIDITY_INCORRECT_SIGNATURE:
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 7fc11e6c3ac9..3c0e0582e8a8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,11 +150,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
- onWindowInfosChangedInternal(windowHandles, displayInfos);
- } else {
- mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
- }
+ onWindowInfosChangedInternal(windowHandles, displayInfos);
}
private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d2546e49267a..3467f947ece4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3212,18 +3212,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* will be ignored.
*/
boolean isUniversalResizeable() {
- if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
- return false;
- }
- final boolean compatEnabled = Flags.universalResizableByDefault()
- && mDisplayContent != null && mDisplayContent.getConfiguration()
- .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
- && mDisplayContent.getIgnoreOrientationRequest()
- && info.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
- if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
- return false;
- }
- if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
+ final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.getConfiguration()
+ .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ && mDisplayContent.getIgnoreOrientationRequest();
+ if (!canBeUniversalResizeable(info.applicationInfo, mWmService, isLargeScreen,
+ true /* forActivity */)) {
return false;
}
if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
@@ -3234,6 +3227,33 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
.userPreferenceCompatibleWithNonResizability();
}
+ /**
+ * Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application
+ * can be ignored.
+ */
+ static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms,
+ boolean isLargeScreen, boolean forActivity) {
+ if (appInfo.category == ApplicationInfo.CATEGORY_GAME) {
+ return false;
+ }
+ final boolean compatEnabled = isLargeScreen && Flags.universalResizableByDefault()
+ && appInfo.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+ final boolean configEnabled = (isLargeScreen
+ ? wms.mConstants.mIgnoreActivityOrientationRequestLargeScreen
+ : wms.mConstants.mIgnoreActivityOrientationRequestSmallScreen)
+ && !wms.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(
+ appInfo.packageName);
+ if (!compatEnabled && !configEnabled) {
+ return false;
+ }
+ if (forActivity) {
+ // The caller will check both application and activity level property.
+ return true;
+ }
+ return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(),
+ appInfo.packageName);
+ }
+
boolean isResizeable() {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
@@ -3667,16 +3687,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pauseKeyDispatchingLocked();
- // We are finishing the top focused activity and its task has nothing to be focused so
- // the next focusable task should be focused.
- if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
- == null) {
- task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
- shouldAdjustGlobalFocus);
- }
-
- finishActivityResults(resultCode, resultData, resultGrants);
-
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
final WindowContainer<?> trigger = endTask ? task : this;
@@ -3687,6 +3697,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (transition != null) {
transition.collectClose(trigger);
}
+ // We are finishing the top focused activity and its task has nothing to be focused so
+ // the next focusable task should be focused.
+ if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
+ == null) {
+ task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
+ shouldAdjustGlobalFocus);
+ }
+
+ finishActivityResults(resultCode, resultData, resultGrants);
+
if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -4593,6 +4613,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else if (fromOrientation != requestedOrientation) {
return false;
}
+
+ // If another activity above the activity which has starting window, allows to steal the
+ // starting window if the above activity isn't drawn.
+ if (task.getChildCount() >= 3
+ && fromActivity.mStartingData.mAssociatedTask == null) {
+ final ActivityRecord aboveFrom = task.getActivityAbove(fromActivity);
+ if (aboveFrom != null && aboveFrom != this && !aboveFrom.mReportedDrawn) {
+ return false;
+ }
+ }
+
// In this case, the starting icon has already been displayed, so start
// letting windows get shown immediately without any more transitions.
if (fromActivity.mVisible) {
@@ -4616,6 +4647,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
tStartingWindow.mToken = this;
tStartingWindow.mActivityRecord = this;
+ if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+ // The removal of starting window should wait for window drawn of current
+ // activity.
+ final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
+ if (mainWin == null || !mainWin.isDrawn()) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+ mStartingData.mPrepareRemoveAnimation = false;
+ }
+ }
+
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Removing starting %s from %s", tStartingWindow, fromActivity);
mTransitionController.collect(tStartingWindow);
@@ -10291,7 +10332,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
.isVisibilityUnknown(this)) {
return false;
}
- if (!isVisibleRequested()) return true;
+ if (!isVisibleRequested()) {
+ // TODO(b/294925498): Remove this finishing check once we have accurate ready tracking.
+ if (task != null && task.getPausingActivity() == this) {
+ // Visibility of starting activities isn't calculated until pause-complete, so if
+ // this is not paused yet, don't consider it ready.
+ return false;
+ }
+ return true;
+ }
if (mPendingRelaunchCount > 0) return false;
// Wait for attach. That is the earliest time where we know if there will be an associated
// display rotation. If we don't wait, the starting-window can finishDrawing first and
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2e2ca147dcdd..2781592c6b4f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -27,6 +27,7 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER;
import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -891,7 +892,10 @@ class ActivityStarter {
final ActivityOptions originalOptions = mRequest.activityOptions != null
? mRequest.activityOptions.getOriginalOptions() : null;
// Only track the launch time of activity that will be resumed.
- launchingRecord = mDoResume ? mLastStartActivityRecord : null;
+ if (mDoResume || (isStartResultSuccessful(res)
+ && mLastStartActivityRecord.getTask().isVisibleRequested())) {
+ launchingRecord = mLastStartActivityRecord;
+ }
// If the new record is the one that started, a new activity has created.
final boolean newActivityCreated = mStartActivity == launchingRecord;
// Notify ActivityMetricsLogger that the activity has launched.
@@ -1838,7 +1842,7 @@ class ActivityStarter {
remoteTransition, null /* displayChange */);
} else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) {
// Do nothing if the activity is started and is resumed directly.
- } else if (isStarted) {
+ } else if (isStarted && (mBalCode != BAL_BLOCK || mDoResume)) {
// Make the collecting transition wait until this request is ready.
if (transition != null) {
transition.setReady(started, false);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8ff08187c698..afa7ea136c77 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1555,7 +1555,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final ActivityRecord[] outActivity = new ActivityRecord[1];
- getActivityStartController().obtainStarter(intent, "dream")
+ final int res = getActivityStartController()
+ .obtainStarter(intent, "dream")
.setCallingUid(callingUid)
.setCallingPid(callingPid)
.setCallingPackage(intent.getPackage())
@@ -1569,9 +1570,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.execute();
final ActivityRecord started = outActivity[0];
- final IAppTask appTask = started == null ? null :
- new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
- return appTask;
+ if (started == null || !ActivityManager.isStartResultSuccessful(res)) {
+ // start the dream activity failed.
+ return null;
+ }
+ return new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
}
}
@@ -3794,6 +3797,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
r.setPictureInPictureParams(params);
enterPipTransition.setPipActivity(r);
r.mAutoEnteringPip = isAutoEnter;
+
+ if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+ && enterPipTransition != null) {
+ enterPipTransition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> {
getTransitionController().requestStartTransition(enterPipTransition,
r.getTask(), null /* remoteTransition */, null /* displayChange */);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 70a8f563275f..a077a0b9a2ca 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2054,6 +2054,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
break;
}
}
+ long timeRemaining = endTime - System.currentTimeMillis();
+ mWindowManager.mSnapshotController.mTaskSnapshotController.waitFlush(timeRemaining);
// Force checkReadyForSleep to complete.
checkReadyForSleepLocked(false /* allowDelay */);
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 203932d4df58..330283fb9ab3 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -77,13 +77,8 @@ class AppCompatController {
mAppCompatOverrides);
mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
// Application level.
- try {
- if (packageManager.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
- mActivityRecord.packageName).getBoolean()) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Fall through.
+ if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) {
+ return true;
}
// Activity level.
try {
@@ -98,6 +93,15 @@ class AppCompatController {
});
}
+ static boolean allowRestrictedResizability(PackageManager pm, String packageName) {
+ try {
+ return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
@NonNull
TransparentPolicy getTransparentPolicy() {
return mTransparentPolicy;
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index ebb50db54693..a41832498880 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -173,7 +173,8 @@ final class AppCompatUtils {
appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
-
+ // TODO(b/379824541) Remove duplicate information.
+ appCompatTaskInfo.topActivityLetterboxBounds = bounds;
// We need to consider if letterboxed or pillarboxed.
// TODO(b/336807329) Encapsulate reachability logic
appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
@@ -282,6 +283,7 @@ final class AppCompatUtils {
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxBounds = null;
info.cameraCompatTaskInfo.freeformCameraCompatMode =
CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
info.clearTopActivityFlags();
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index fcaab2c0f8c7..601b17c46c03 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Build;
@@ -40,6 +41,8 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -76,6 +79,7 @@ class AppWarnings {
public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
+ public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10;
/**
* Map of package flags for each user.
@@ -101,6 +105,7 @@ class AppWarnings {
private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
+ private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs;
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
@@ -250,6 +255,19 @@ class AppWarnings {
}
}
+ public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) {
+ // Don't show dialog if the app compat is enabled using property
+ final boolean appCompatEnabled = SystemProperties.getBoolean(
+ "bionic.linker.16kb.app_compat.enabled", false);
+ if (appCompatEnabled) {
+ return;
+ }
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+ if (is16KbDevice) {
+ mUiHandler.showPageSizeMismatchDialog(r);
+ }
+ }
+
/**
* Called when an activity is being started.
*
@@ -260,6 +278,9 @@ class AppWarnings {
showUnsupportedDisplaySizeDialogIfNeeded(r);
showDeprecatedTargetDialogIfNeeded(r);
showDeprecatedAbiDialogIfNeeded(r);
+ if (Flags.appCompatOption16kb()) {
+ showPageSizeMismatchDialogIfNeeded(r);
+ }
}
/**
@@ -457,6 +478,41 @@ class AppWarnings {
}
}
+ @UiThread
+ private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) {
+ String warning =
+ mAtm.mContext
+ .getPackageManager()
+ .getPageSizeCompatWarningMessage(ar.info.packageName);
+ if (warning == null) {
+ return;
+ }
+
+ final int userId = getUserIdForActivity(ar);
+ PageSizeMismatchDialog pageSizeMismatchDialog;
+ if (mPageSizeMismatchDialogs != null) {
+ pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+ if (pageSizeMismatchDialog != null) {
+ pageSizeMismatchDialog.dismiss();
+ mPageSizeMismatchDialogs.remove(userId);
+ }
+ }
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
+ pageSizeMismatchDialog =
+ new PageSizeMismatchDialog(
+ AppWarnings.this,
+ getUiContextForActivity(ar),
+ ar.info.applicationInfo,
+ userId,
+ warning);
+ pageSizeMismatchDialog.show();
+ if (mPageSizeMismatchDialogs == null) {
+ mPageSizeMismatchDialogs = new SparseArray<>();
+ }
+ mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog);
+ }
+ }
+
/**
* Dismisses all warnings for the given package.
* <p>
@@ -510,6 +566,16 @@ class AppWarnings {
mDeprecatedAbiDialogs.remove(userId);
}
}
+
+ // Hides the "page size app compat" dialog if necessary.
+ if (mPageSizeMismatchDialogs != null) {
+ PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+ if (pageSizeMismatchDialog != null
+ && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) {
+ pageSizeMismatchDialog.dismiss();
+ mPageSizeMismatchDialogs.remove(userId);
+ }
+ }
}
/**
@@ -649,6 +715,7 @@ class AppWarnings {
private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6;
+ private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7;
public UiHandler(Looper looper) {
super(looper, null, true);
@@ -681,6 +748,10 @@ class AppWarnings {
final ActivityRecord ar = (ActivityRecord) msg.obj;
showDeprecatedAbiDialogUiThread(ar);
} break;
+ case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showPageSizeMismatchDialogUiThread(ar);
+ } break;
}
}
@@ -712,6 +783,11 @@ class AppWarnings {
public void hideDialogsForPackage(String name, int userId) {
obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
}
+
+ public void showPageSizeMismatchDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG);
+ obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget();
+ }
}
static class BaseDialog {
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index f0a6e9ec1d4f..dd1af0a497ca 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -653,7 +653,9 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// by drawing the rotated content before applying projection transaction of display.
// And it will fade in after the display transition is finished.
if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
- && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
+ && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()
+ && !mService.mAtmService.mBackNavigationController.hasFixedRotationAnimation(
+ mDisplayContent)) {
hideImmediately(w.mToken, Operation.ACTION_FADE);
if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4ed8b09cd652..3968b525f11d 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -622,6 +622,15 @@ class BackNavigationController {
}
}
+ boolean hasFixedRotationAnimation(@NonNull DisplayContent displayContent) {
+ if (!mAnimationHandler.mComposed) {
+ return false;
+ }
+ final ActivityRecord openActivity = mAnimationHandler.mOpenActivities[0];
+ return displayContent == openActivity.mDisplayContent
+ && displayContent.isFixedRotationLaunchingApp(openActivity);
+ }
+
private boolean isWaitBackTransition() {
// Ignore mWaitTransition while flag is enabled.
return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 506477f67bfc..ae65db46b242 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -38,7 +38,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.os.RemoteException;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -65,6 +67,9 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
@NonNull
private final CameraStateMonitor mCameraStateMonitor;
+ // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+ // all current camera activities, especially when the camera access is switching from one app to
+ // another.
@Nullable
private Task mCameraTask;
@@ -123,8 +128,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
// Do not check orientation outside of the config recompute, as the app's orientation intent
// might be obscured by a fullscreen override. Especially for apps which have a camera
// functionality which is not the main focus of the app: while most of the app might work
@@ -136,18 +140,15 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
return;
}
- cameraActivity.recomputeConfiguration();
- cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true);
- cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false);
+ mCameraTask = cameraActivity.getTask();
+ updateAndDispatchCameraConfiguration();
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
// Top activity in the same task as the camera activity, or `null` if the task is
// closed.
- final ActivityRecord topActivity = mCameraTask != null
- ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
- : null;
+ final ActivityRecord topActivity = getTopActivityFromCameraTask();
if (topActivity != null) {
if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_STATES,
@@ -157,16 +158,61 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
return false;
}
}
- mCameraTask = null;
return true;
}
+ @Override
+ public void onCameraClosed() {
+ // Top activity in the same task as the camera activity, or `null` if the task is
+ // closed.
+ final ActivityRecord topActivity = getTopActivityFromCameraTask();
+ // Only clean up if the camera is not running - this close signal could be from switching
+ // cameras (e.g. back to front camera, and vice versa).
+ if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+ updateAndDispatchCameraConfiguration();
+ mCameraTask = null;
+ }
+ }
+
+ private void updateAndDispatchCameraConfiguration() {
+ if (mCameraTask == null) {
+ return;
+ }
+ final ActivityRecord activity = getTopActivityFromCameraTask();
+ if (activity != null) {
+ activity.recomputeConfiguration();
+ mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+ updateCompatibilityInfo(activity);
+ activity.ensureActivityConfiguration(/* ignoreVisibility= */ true);
+ } else {
+ mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+ }
+ }
+
+ private void updateCompatibilityInfo(@NonNull ActivityRecord activityRecord) {
+ final CompatibilityInfo compatibilityInfo = activityRecord.mAtmService
+ .compatibilityInfoForPackageLocked(activityRecord.info.applicationInfo);
+ compatibilityInfo.applicationDisplayRotation =
+ CameraCompatTaskInfo.getDisplayRotationFromCameraCompatMode(
+ getCameraCompatMode(activityRecord));
+ try {
+ // TODO(b/380840084): Consider using a ClientTransaction for this update.
+ activityRecord.app.getThread().updatePackageCompatibilityInfo(
+ activityRecord.packageName, compatibilityInfo);
+ } catch (RemoteException e) {
+ ProtoLog.w(WmProtoLogGroups.WM_DEBUG_STATES,
+ "Unable to update CompatibilityInfo for app %s", activityRecord.app);
+ }
+ }
+
boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
return isCameraRunningAndWindowingModeEligible(activity);
}
boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return activity.inFreeformWindowingMode()
+ return activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldApplyFreeformTreatmentForCameraCompat()
+ && activity.inFreeformWindowingMode()
&& mCameraStateMonitor.isCameraRunningForActivity(activity);
}
@@ -262,10 +308,17 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
&& !activity.isEmbedded();
}
+ @Nullable
+ private ActivityRecord getTopActivityFromCameraTask() {
+ return mCameraTask != null
+ ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
+ : null;
+ }
+
private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
@NonNull String cameraId) {
if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true)
- || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
+ || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 3b6e30ab2a6d..3aa355869d85 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -67,6 +67,10 @@ class CameraStateMonitor {
// when camera connection is closed and we need to clean up our records.
private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping =
new CameraIdPackageNameBiMapping();
+ // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to
+ // keep track of camera-closed signals when apps are switching camera access, so that the policy
+ // can restore app configuration when an app closes camera (e.g. loses camera access due to
+ // another app).
private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
// TODO(b/336474959): should/can this go in the compat listeners?
@@ -163,15 +167,14 @@ class CameraStateMonitor {
if (cameraActivity == null || cameraActivity.getTask() == null) {
return;
}
- notifyListenersCameraOpened(cameraActivity, cameraId);
+ notifyListenersCameraOpened(cameraActivity);
}
}
- private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
CameraCompatStateListener listener = mCameraStateListeners.get(i);
- listener.onCameraOpened(cameraActivity, cameraId);
+ listener.onCameraOpened(cameraActivity);
}
}
@@ -224,11 +227,11 @@ class CameraStateMonitor {
// Already reconnected to this camera, no need to clean up.
return;
}
-
- final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
- if (closeSuccessfulForAllListeners) {
+ final boolean canClose = checkCanCloseForAllListeners(cameraId);
+ if (canClose) {
// Finish cleaning up.
mCameraIdPackageBiMapping.removeCameraId(cameraId);
+ notifyListenersCameraClosed();
} else {
// Not ready to process closure yet - the camera activity might be refreshing.
// Try again later.
@@ -238,15 +241,21 @@ class CameraStateMonitor {
}
/**
- * @return {@code false} if any listeners have reported issues processing the close.
+ * @return {@code false} if any listener has reported that they cannot process camera close now.
*/
- private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
- boolean closeSuccessfulForAllListeners = true;
+ private boolean checkCanCloseForAllListeners(@NonNull String cameraId) {
for (int i = 0; i < mCameraStateListeners.size(); i++) {
- closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
+ if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) {
+ return false;
+ }
}
+ return true;
+ }
- return closeSuccessfulForAllListeners;
+ private void notifyListenersCameraClosed() {
+ for (int i = 0; i < mCameraStateListeners.size(); i++) {
+ mCameraStateListeners.get(i).onCameraClosed();
+ }
}
// TODO(b/335165310): verify that this works in multi instance and permission dialogs.
@@ -297,14 +306,18 @@ class CameraStateMonitor {
/**
* Notifies the compat listener that an activity has opened camera.
*/
- // TODO(b/336474959): try to decouple `cameraId` from the listeners.
- void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+ void onCameraOpened(@NonNull ActivityRecord cameraActivity);
/**
- * Notifies the compat listener that camera is closed.
+ * Checks whether a listener is ready to do a cleanup when camera is closed.
*
- * @return true if cleanup has been successful - the notifier might try again if false.
+ * <p>The notifier might try again if false is returned.
*/
// TODO(b/336474959): try to decouple `cameraId` from the listeners.
- boolean onCameraClosed(@NonNull String cameraId);
+ boolean canCameraBeClosed(@NonNull String cameraId);
+
+ /**
+ * Notifies the compat listener that camera is closed.
+ */
+ void onCameraClosed();
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 93ccd74c6b23..6ccceb9cf564 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -243,6 +243,19 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ /** Called when the surface of display is changed to a different instance. */
+ void resetRecordingDisplay(int displayId) {
+ if (!isCurrentlyRecording()
+ || mContentRecordingSession.getDisplayToRecord() != displayId) {
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Display %d changed surface so stop recording", displayId);
+ mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
+ mRecordedSurface = null;
+ // Do not un-set the token, in case new surface is ready and recording should begin again.
+ }
+
/**
* Pauses recording on this display content. Note the session does not need to be updated,
* since recording can be resumed still.
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 4e79e377a2a3..37e8f6260420 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -424,6 +424,7 @@ class DeferredDisplayUpdater {
|| first.brightnessMinimum != second.brightnessMinimum
|| first.brightnessMaximum != second.brightnessMaximum
|| first.brightnessDefault != second.brightnessDefault
+ || first.brightnessDim != second.brightnessDim
|| first.installOrientation != second.installOrientation
|| first.isForceSdr != second.isForceSdr
|| !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9a33df13bb6a..0b661580f450 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -791,6 +791,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private WindowState mLastWakeLockHoldingWindow;
/**
+ * Whether display is allowed to ignore all activity size restrictions.
+ * @see #isDisplayIgnoreActivitySizeRestrictions
+ */
+ private final boolean mIgnoreActivitySizeRestrictions;
+
+ /**
* The helper of policy controller.
*
* @see DisplayWindowPolicyControllerHelper
@@ -1220,6 +1226,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ mIgnoreActivitySizeRestrictions =
+ mWmService.mDisplayWindowSettings.isIgnoreActivitySizeRestrictionsLocked(this);
// Sets the initial touch mode state.
mInTouchMode = mWmService.mContext.getResources().getBoolean(
@@ -1264,7 +1272,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
-
+ // Reset the recording displays which were mirroring this display.
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ final ContentRecorder recorder = mRootWindowContainer.getChildAt(i).mContentRecorder;
+ if (recorder != null) {
+ recorder.resetRecordingDisplay(mDisplayId);
+ }
+ }
mLastSurfacePosition.set(0, 0);
mLastDeltaRotation = Surface.ROTATION_0;
@@ -2266,7 +2280,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
- getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
// The sync transaction should already contains setDisplayProjection, so unset the
// hint to restore the natural state when the transaction is applied.
transaction.unsetFixedTransformHint(mSurfaceControl);
@@ -2276,6 +2290,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
}
+ void setFixedTransformHint(Transaction t, SurfaceControl sc, int rotation) {
+ t.setFixedTransformHint(sc, (rotation + mDisplayInfo.installOrientation) % 4);
+ }
+
void configureDisplayPolicy() {
mRootWindowContainer.updateDisplayImePolicyCache();
mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
@@ -4267,7 +4285,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
final int imePolicy = mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
- if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY && forceDesktopMode()) {
+ if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY
+ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
// If the display has not explicitly requested for the IME to be hidden then it shall
// show the IME locally.
return DISPLAY_IME_POLICY_LOCAL;
@@ -4275,10 +4294,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return imePolicy;
}
- boolean forceDesktopMode() {
- return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
- }
-
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
if (mInputMethodWindow == null) {
@@ -4871,7 +4886,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** @return {@code true} if there is window to wait before enabling the screen. */
boolean shouldWaitForSystemDecorWindowsOnBoot() {
- if (!isDefaultDisplay && !supportsSystemDecorations()) {
+ if (!isDefaultDisplay && !isSystemDecorationsSupported()) {
// Nothing to wait because the secondary display doesn't support system decorations,
// there is no wallpaper, keyguard (status bar) or application (home) window to show
// during booting.
@@ -5750,22 +5765,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
*/
- boolean supportsSystemDecorations() {
- boolean forceDesktopModeOnDisplay = forceDesktopMode();
-
- if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
- // System decorations should not be forced on a rear display due to security policies.
- forceDesktopModeOnDisplay =
- forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+ boolean isSystemDecorationsSupported() {
+ if (mDisplayId == mWmService.mVr2dDisplayId) {
+ // VR virtual display will be used to run and render 2D app within a VR experience.
+ return false;
}
+ if (!isTrusted()) {
+ // Do not show system decorations on untrusted virtual display.
+ return false;
+ }
+ if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
+ || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ // This display is configured to show system decorations.
+ return true;
+ }
+ if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+ if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+ // System decorations should not be forced on a rear display due to security
+ // policies.
+ return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+ }
+ // If the display is forced to desktop mode, treat it the same as it is configured to
+ // show system decorations.
+ return true;
+ }
+ return false;
+ }
- return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
- || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || forceDesktopModeOnDisplay)
- // VR virtual display will be used to run and render 2D app within a VR experience.
- && mDisplayId != mWmService.mVr2dDisplayId
- // Do not show system decorations on untrusted virtual display.
- && isTrusted();
+ /**
+ * This is the development option to force enable desktop mode on all secondary public displays
+ * that are not owned by a virtual device.
+ * When this is enabled, it also force enable system decorations on those displays.
+ *
+ * If we need a per-display config to enable desktop mode for production, that config should
+ * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
+ */
+ boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
+ if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+ return false;
+ }
+ // Desktop mode is not supported on virtual devices.
+ int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
+ return deviceId == Context.DEVICE_ID_DEFAULT;
}
/**
@@ -5776,7 +5817,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
boolean isHomeSupported() {
return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
- || supportsSystemDecorations();
+ || isSystemDecorationsSupported();
}
/**
@@ -5787,7 +5828,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* {@link VirtualDisplayConfig.Builder#setIgnoreActivitySizeRestrictions}.</p>
*/
boolean isDisplayIgnoreActivitySizeRestrictions() {
- return mWmService.mDisplayWindowSettings.isIgnoreActivitySizeRestrictionsLocked(this);
+ return mIgnoreActivitySizeRestrictions;
}
/**
@@ -7061,12 +7102,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void setImeInputTargetRequestedVisibility(boolean visible) {
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// TODO(b/353463205) we won't have the statsToken in all cases, but should still log
try {
- mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
+ mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible,
+ statsToken);
} catch (RemoteException e) {
+ // TODO(b/353463205) fail statsToken
Slog.w(TAG, "Failed to deliver setImeInputTargetRequestedVisibility", e);
}
}
@@ -7075,9 +7119,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
- if (mRequestedVisibleTypes != requestedVisibleTypes) {
- mRequestedVisibleTypes = requestedVisibleTypes;
+ void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+ int newRequestedVisibleTypes =
+ (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
+ if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+ mRequestedVisibleTypes = newRequestedVisibleTypes;
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 76e8a70768c1..659bb6784c89 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -659,7 +659,7 @@ public class DisplayPolicy {
}
} else {
mHasStatusBar = false;
- mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
+ mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
}
mRefreshRatePolicy = new RefreshRatePolicy(mService,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index df209ff4cf50..f53bc700de05 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -295,7 +295,7 @@ public class DisplayRotation {
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
- mDefaultDisplayRotationChangedCallback);
+ displayContent.getDisplayId(), mDefaultDisplayRotationChangedCallback);
}
if (isDefaultDisplay) {
@@ -445,7 +445,8 @@ public class DisplayRotation {
final boolean isTv = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
mDefaultFixedToUserRotation =
- (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+ (isCar || isTv || mService.mIsPc
+ || mDisplayContent.isPublicSecondaryDisplayWithDesktopModeForceEnabled()
|| !mDisplayContent.shouldRotateWithContent())
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -1659,7 +1660,8 @@ public class DisplayRotation {
void removeDefaultDisplayRotationChangedCallback() {
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
- mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 0ccc0fe80b52..3c199dba565b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -71,6 +71,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
@NonNull
private final ActivityRefresher mActivityRefresher;
+ // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+ // all current camera activities, especially when the camera access is switching from one app to
+ // another.
@Nullable
private Task mCameraTask;
@@ -327,8 +330,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
mCameraTask = cameraActivity.getTask();
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
@@ -374,16 +376,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
- final ActivityRecord topActivity;
- if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
- topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
- /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
- } else {
- topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
- }
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
+ final ActivityRecord topActivity = getTopActivity();
- mCameraTask = null;
if (topActivity == null) {
return true;
}
@@ -399,6 +394,23 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return false;
}
}
+ return true;
+ }
+
+ @Override
+ public void onCameraClosed() {
+ final ActivityRecord topActivity = getTopActivity();
+
+ // Only clean up if the camera is not running - this close signal could be from switching
+ // cameras (e.g. back to front camera, and vice versa).
+ if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+ // Call after getTopActivity(), as that method might use the activity from mCameraTask.
+ mCameraTask = null;
+ }
+
+ if (topActivity == null) {
+ return;
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera is closed, updating rotation.",
@@ -406,11 +418,10 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
// Checking whether an activity in fullscreen rather than the task as this camera compat
// treatment doesn't cover activity embedding.
if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
- return true;
+ return;
}
recomputeConfigurationForCameraCompatIfNeeded(topActivity);
mDisplayContent.updateOrientation();
- return true;
}
// TODO(b/336474959): Do we need cameraId here?
@@ -430,6 +441,16 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
}
}
+ @Nullable
+ private ActivityRecord getTopActivity() {
+ if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
+ return mCameraTask != null ? mCameraTask.getTopActivity(
+ /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
+ } else {
+ return mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
+ }
+ }
+
/**
* @return {@code true} if the configuration needs to be recomputed after a camera state update.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
index ae3787cffa23..01e1b1342989 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import android.view.Display;
import android.view.Surface;
@@ -40,6 +41,7 @@ class DisplayRotationCoordinator {
@Nullable
@VisibleForTesting
Runnable mDefaultDisplayRotationChangedCallback;
+ private int mCallbackDisplayId = Display.INVALID_DISPLAY;
@Surface.Rotation
private int mDefaultDisplayCurrentRotation;
@@ -68,12 +70,15 @@ class DisplayRotationCoordinator {
* Register a callback to be notified when the default display's rotation changes. Clients can
* query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
*/
- void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
- if (mDefaultDisplayRotationChangedCallback != null) {
- throw new UnsupportedOperationException("Multiple clients unsupported");
+ void setDefaultDisplayRotationChangedCallback(int displayId, @NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null && displayId != mCallbackDisplayId) {
+ throw new UnsupportedOperationException("Multiple clients unsupported"
+ + ". Incoming displayId: " + displayId
+ + ", existing displayId: " + mCallbackDisplayId);
}
mDefaultDisplayRotationChangedCallback = callback;
+ mCallbackDisplayId = displayId;
if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
callback.run();
@@ -82,10 +87,17 @@ class DisplayRotationCoordinator {
/**
* Removes the callback that was added via
- * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ * {@link #setDefaultDisplayRotationChangedCallback(int, Runnable)}.
*/
- void removeDefaultDisplayRotationChangedCallback() {
+ void removeDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (callback != mDefaultDisplayRotationChangedCallback) {
+ Slog.w(TAG, "Attempted to remove non-matching callback."
+ + " DisplayId: " + mCallbackDisplayId);
+ return;
+ }
+
mDefaultDisplayRotationChangedCallback = null;
+ mCallbackDisplayId = Display.INVALID_DISPLAY;
}
static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c87b811b4231..f6d05d08cb04 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -143,7 +143,7 @@ class DisplayWindowSettings {
}
// No record is present so use default windowing mode policy.
final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
- && (mService.mIsPc || dc.forceDesktopMode());
+ && (mService.mIsPc || dc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
if (forceFreeForm) {
return WindowConfiguration.WINDOWING_MODE_FREEFORM;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2a5a3a5638d2..1c4e487d2e7e 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -528,7 +528,7 @@ class DragState {
}
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = null;
- if (interceptsGlobalDrag) {
+ if (interceptsGlobalDrag && mData != null) {
data = mData.copyForTransferWithActivityInfo();
PersistableBundle extras = data.getDescription().getExtras() != null
? data.getDescription().getExtras()
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 48e1c069821c..4230cd868c03 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -40,6 +40,7 @@ import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.protolog.ProtoLog;
import java.io.PrintWriter;
@@ -285,7 +286,12 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (isImeInputTarget(caller)) {
reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken);
} else {
- // TODO(b/353463205) add ImeTracker?
+ ProtoLog.w(WM_DEBUG_IME,
+ "Tried to update client visibility for non-IME input target %s "
+ + "(current target: %s)",
+ caller, mDisplayContent.getImeInputTarget());
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return false;
@@ -316,15 +322,21 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (Flags.refactorInsetsController() && target != null) {
InsetsControlTarget imeControlTarget = getControlTarget();
if (target != imeControlTarget) {
- // TODO(b/353463205): start new request here?
+ // TODO(b/353463205): check if fromUser=false is correct here
+ boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime());
+ ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_SERVER,
+ imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED
+ : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
+ false /* fromUser */);
reportImeInputTargetStateToControlTarget(target, imeControlTarget,
- null /* statsToken */);
+ statsToken);
}
}
}
private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget,
- InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken) {
+ InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) {
// In case of the multi window mode, update the requestedVisibleTypes from
// the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController.
// Then, trigger onRequestedVisibleTypesChanged for the controlTarget with
@@ -333,7 +345,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (controlTarget != null) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
- controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+ controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
} else if (imeInsetsTarget instanceof InsetsControlTarget) {
// In case of a virtual display that cannot show the IME, the
// controlTarget will be null here, as no controlTarget was set yet. In
@@ -345,7 +357,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (controlTarget != imeInsetsTarget) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
- controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+ controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
// not all virtual displays have an ImeInsetsSourceProvider, so it is not
// guaranteed that the IME will be started when the control target reports its
// requested visibility back. Thus, invoking the listener here.
@@ -390,9 +402,9 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
WindowToken imeToken = mWindowContainer.asWindowState() != null
? mWindowContainer.asWindowState().mToken : null;
final var rotationController = mDisplayContent.getAsyncRotationController();
- if ((rotationController != null && rotationController.isTargetToken(imeToken))
- || (imeToken != null && imeToken.isSelfAnimating(
- 0 /* flags */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
+ if ((rotationController != null && rotationController.isTargetToken(imeToken)) || (
+ imeToken != null && imeToken.isSelfAnimating(0 /* flags */,
+ SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
// Skip reporting IME drawn state when the control target is in fixed
// rotation, AsyncRotationController will report after the animation finished.
return;
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 7043aacfc44d..cee49676eeae 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
@@ -90,8 +91,10 @@ interface InsetsControlTarget extends InsetsTarget {
/**
* @param visible the requested visibility for the IME, used for
* {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}
+ * @param statsToken the token tracking the current IME request
*/
- default void setImeInputTargetRequestedVisibility(boolean visible) {
+ default void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
}
/** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 2401f9072ea3..98521d36ad44 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -19,6 +19,8 @@ yunfanc@google.com
wilsonshih@google.com
jiamingliu@google.com
pdwilliams@google.com
+charlesccchen@google.com
+marziana@google.com
# Files related to background activity launches
per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
new file mode 100644
index 000000000000..8c50913dd563
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wm;
+
+import static android.text.Html.FROM_HTML_MODE_COMPACT;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.text.Html;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+
+/**
+ * Show warning dialog when
+ * - Uncompressed libs inside apk are not aligned to page size
+ * - ELF Load segments are not page size aligned
+ * This dialog will be shown everytime when app is launched. Apps can choose to override
+ * by setting compat mode pageSizeCompat="enabled" in manifest or "disabled" to opt out.
+ * Both cases will skip the PageSizeMismatchDialog.
+ *
+ */
+class PageSizeMismatchDialog extends AppWarnings.BaseDialog {
+ PageSizeMismatchDialog(
+ final AppWarnings manager,
+ Context context,
+ ApplicationInfo appInfo,
+ int userId,
+ String warning) {
+ super(manager, context, appInfo.packageName, userId);
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label =
+ appInfo.loadSafeLabel(
+ pm,
+ PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
+ PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE
+ | PackageItemInfo.SAFE_LABEL_FLAG_TRIM);
+
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(context)
+ .setPositiveButton(
+ R.string.ok,
+ (dialog, which) -> {/* Do nothing */})
+ .setMessage(Html.fromHtml(warning, FROM_HTML_MODE_COMPACT))
+ .setTitle(label);
+
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 27f82d90fdac..7fdc2c67b5ce 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -40,7 +40,7 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
@@ -1529,7 +1529,7 @@ class RecentTasks {
// The Recents is only supported on default display now, we should only keep the
// most recent task of home display.
boolean isMostRecentTask;
- if (enableRefactorTaskThumbnail()) {
+ if (enableUseTopVisibleActivityForExcludeFromRecentTask()) {
isMostRecentTask = task.getTopVisibleActivity() != null;
} else {
isMostRecentTask = taskIndex == 0;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c89feb41e723..46312aff1fb6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2853,11 +2853,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void prepareForShutdown() {
+ mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
for (int i = 0; i < getChildCount(); i++) {
- final int displayId = getChildAt(i).mDisplayId;
- mWindowManager.mSnapshotController.mTaskSnapshotController
- .snapshotForShutdown(displayId);
- createSleepToken("shutdown", displayId);
+ createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
}
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c20b85858c44..8f0f6860ceb6 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -56,7 +56,7 @@ public class SeamlessRotator {
mOldRotation = oldRotation;
mNewRotation = newRotation;
mApplyFixedTransformHint = applyFixedTransformationHint;
- mFixedTransformHint = oldRotation;
+ mFixedTransformHint = (oldRotation + info.installOrientation) % 4;
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
final int pH = flipped ? info.logicalWidth : info.logicalHeight;
final int pW = flipped ? info.logicalHeight : info.logicalWidth;
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index bd8e8f4008de..8b63ecf7135f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -103,12 +103,42 @@ class SnapshotPersistQueue {
}
/**
- * Write out everything in the queue because of shutdown.
+ * Prepare to enqueue all visible task snapshots because of shutdown.
*/
- void shutdown() {
+ void prepareShutdown() {
synchronized (mLock) {
mShutdown = true;
- mLock.notifyAll();
+ }
+ }
+
+ private boolean isQueueEmpty() {
+ synchronized (mLock) {
+ return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
+ }
+ }
+
+ void waitFlush(long timeout) {
+ if (timeout <= 0) {
+ return;
+ }
+ final long endTime = System.currentTimeMillis() + timeout;
+ while (true) {
+ if (!isQueueEmpty()) {
+ long timeRemaining = endTime - System.currentTimeMillis();
+ if (timeRemaining > 0) {
+ synchronized (mLock) {
+ try {
+ mLock.wait(timeRemaining);
+ } catch (InterruptedException e) {
+ }
+ }
+ } else {
+ Slog.w(TAG, "Snapshot Persist Queue flush timed out");
+ break;
+ }
+ } else {
+ break;
+ }
}
}
@@ -139,7 +169,9 @@ class SnapshotPersistQueue {
mWriteQueue.addLast(item);
}
item.onQueuedLocked();
- ensureStoreQueueDepthLocked();
+ if (!mShutdown) {
+ ensureStoreQueueDepthLocked();
+ }
if (!mPaused) {
mLock.notifyAll();
}
@@ -213,6 +245,9 @@ class SnapshotPersistQueue {
if (!writeQueueEmpty && !mPaused) {
continue;
}
+ if (mShutdown && writeQueueEmpty) {
+ mLock.notifyAll();
+ }
try {
mQueueIdling = writeQueueEmpty;
mLock.wait();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 87e472ab7aa6..810aa0454246 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3425,6 +3425,7 @@ class Task extends TaskFragment {
info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
+ info.isActivityStackTransparent = !topTask.forAllActivities(r -> (r.occludesParent()));
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
final WindowState windowState = top != null
? top.findMainWindow(/* includeStartingApp= */ false) : null;
@@ -4488,7 +4489,7 @@ class Task extends TaskFragment {
}
void onPictureInPictureParamsChanged() {
- if (inPinnedWindowingMode()) {
+ if (inPinnedWindowingMode() || Flags.enableDesktopWindowingPip()) {
dispatchTaskInfoChangedIfNeeded(true /* force */);
}
}
@@ -6292,12 +6293,6 @@ class Task extends TaskFragment {
return mAnimatingActivityRegistry;
}
- @Override
- void executeAppTransition(ActivityOptions options) {
- mDisplayContent.executeAppTransition();
- ActivityOptions.abort(options);
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e090b1980c6d..51b8bd1f0091 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2129,7 +2129,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
void executeAppTransition(ActivityOptions options) {
- // No app transition applied to the task fragment.
+ mDisplayContent.executeAppTransition();
+ ActivityOptions.abort(options);
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9fe3f7563902..c130931277fe 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -309,23 +309,31 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
/**
* Record task snapshots before shutdown.
*/
- void snapshotForShutdown(int displayId) {
+ void prepareShutdown() {
if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
+ // Make write items run in a batch.
+ mPersister.mSnapshotPersistQueue.setPaused(true);
+ mPersister.mSnapshotPersistQueue.prepareShutdown();
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final TaskSnapshot snapshot = captureSnapshot(task);
+ if (snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ }
+ }
+ }, true /* traverseTopToBottom */);
+ }
+ mPersister.mSnapshotPersistQueue.setPaused(false);
+ }
+
+ void waitFlush(long timeout) {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- displayContent.forAllLeafTasks(task -> {
- if (task.isVisible() && !task.isActivityTypeHome()) {
- final TaskSnapshot snapshot = captureSnapshot(task);
- if (snapshot != null) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- }
- }
- }, true /* traverseTopToBottom */);
- mPersister.mSnapshotPersistQueue.shutdown();
+ mPersister.mSnapshotPersistQueue.waitFlush(timeout);
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 20481f25fa5c..1fc609b7d03a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1429,7 +1429,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Commit wallpaper visibility after activity, because usually the wallpaper target token is
// an activity, and wallpaper's visibility depends on activity's visibility.
for (int i = mParticipants.size() - 1; i >= 0; --i) {
- final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ WallpaperWindowToken wt = wc.asWallpaperToken();
+ if (!Flags.ensureWallpaperInTransitions()) {
+ if (wt == null) {
+ final WindowState windowState = wc.asWindowState();
+ if (windowState != null) {
+ wt = windowState.mToken.asWallpaperToken();
+ }
+ }
+ }
if (wt == null) continue;
final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8562bb23b30f..f3c03cbfb3b4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -791,19 +791,30 @@ class TransitionController {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
ActivityManager.RunningTaskInfo startTaskInfo = null;
- ActivityManager.RunningTaskInfo pipTaskInfo = null;
+ TransitionRequestInfo.PipChange pipChange = null;
if (startTask != null) {
startTaskInfo = startTask.getTaskInfo();
}
// set the pip task in the request if provided
if (transition.getPipActivity() != null) {
- pipTaskInfo = transition.getPipActivity().getTask().getTaskInfo();
+ ActivityManager.RunningTaskInfo pipTaskInfo =
+ transition.getPipActivity().getTask().getTaskInfo();
+ ActivityRecord pipActivity = transition.getPipActivity();
+ if (pipActivity.getTaskFragment() != null
+ && pipActivity.getTaskFragment() != pipActivity.getTask()) {
+ // If the PiP activity is in a TF different from its task, this could be
+ // AE-to-PiP case, so PipChange will have the TF token cached separately.
+ pipChange = new TransitionRequestInfo.PipChange(pipActivity.getTaskFragment()
+ .mRemoteToken.toWindowContainerToken(), pipTaskInfo);
+ } else {
+ pipChange = new TransitionRequestInfo.PipChange(pipTaskInfo);
+ }
transition.setPipActivity(null);
}
final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
- startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
+ startTaskInfo, pipChange, remoteTransition, displayChange,
transition.getFlags(), transition.getSyncId());
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2397e032fcc8..aa60f939f9aa 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3733,7 +3733,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
- getPendingTransaction().setFixedTransformHint(mSurfaceControl,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 31ca24c46f4b..3ad9b62ef058 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -36,7 +36,15 @@ import java.util.concurrent.Executor;
*/
final class WindowManagerConstants {
- /** The orientation of activity will be always "unspecified" except for game apps. */
+ /**
+ * The orientation of activity will be always "unspecified" except for game apps.
+ * <p>Possible values:
+ * <ul>
+ * <li>false: applies to no apps (default)</li>
+ * <li>true: applies to all apps</li>
+ * <li>large: applies to all apps but only on large screens</li>
+ * </ul>
+ */
private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
"ignore_activity_orientation_request";
@@ -69,7 +77,8 @@ final class WindowManagerConstants {
boolean mSystemGestureExcludedByPreQStickyImmersive;
/** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */
- boolean mIgnoreActivityOrientationRequest;
+ boolean mIgnoreActivityOrientationRequestLargeScreen;
+ boolean mIgnoreActivityOrientationRequestSmallScreen;
/** @see #KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST */
private ArraySet<String> mOptOutIgnoreActivityOrientationRequestPackages;
@@ -177,9 +186,12 @@ final class WindowManagerConstants {
}
private void updateIgnoreActivityOrientationRequest() {
- mIgnoreActivityOrientationRequest = mDeviceConfig.getBoolean(
+ final String value = mDeviceConfig.getProperty(
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
+ KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
+ mIgnoreActivityOrientationRequestSmallScreen = Boolean.parseBoolean(value);
+ mIgnoreActivityOrientationRequestLargeScreen = mIgnoreActivityOrientationRequestSmallScreen
+ || ("large".equals(value));
}
private void updateOptOutIgnoreActivityOrientationRequestList() {
@@ -196,8 +208,7 @@ final class WindowManagerConstants {
}
boolean isPackageOptOutIgnoreActivityOrientationRequest(String packageName) {
- return mIgnoreActivityOrientationRequest
- && mOptOutIgnoreActivityOrientationRequestPackages != null
+ return mOptOutIgnoreActivityOrientationRequestPackages != null
&& mOptOutIgnoreActivityOrientationRequestPackages.contains(packageName);
}
@@ -211,7 +222,8 @@ final class WindowManagerConstants {
pw.print(" "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
- pw.print("="); pw.println(mIgnoreActivityOrientationRequest);
+ pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "true"
+ : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "false");
if (mOptOutIgnoreActivityOrientationRequestPackages != null) {
pw.print(" "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST);
pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a0c0b9836507..bf4cb4543e44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -69,6 +69,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -101,6 +102,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.window.WindowProviderService.isWindowProviderService;
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
@@ -157,9 +159,9 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.INPUT_METHOD_W
import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
-import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
import android.Manifest;
import android.Manifest.permission;
@@ -4668,21 +4670,26 @@ public class WindowManagerService extends IWindowManager.Stub
@EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
- public void updateDisplayWindowRequestedVisibleTypes(
- int displayId, @InsetsType int requestedVisibleTypes) {
+ public void updateDisplayWindowRequestedVisibleTypes(int displayId,
+ @InsetsType int visibleTypes, @InsetsType int mask,
+ @Nullable ImeTracker.Token statsToken) {
updateDisplayWindowRequestedVisibleTypes_enforcePermission();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
return;
}
- dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
+ dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
- // IME provider. Check if we have to create a new request here
+ // IME provider. Check if we have to create a new request here, if null.
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
- dc.mRemoteInsetsControlTarget, null /* statsToken */);
+ dc.mRemoteInsetsControlTarget, statsToken);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7696,7 +7703,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ "not exist: %d", displayId);
return false;
}
- return displayContent.supportsSystemDecorations();
+ return displayContent.isSystemDecorationsSupported();
}
}
@@ -7943,43 +7950,46 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) {
Objects.requireNonNull(message.getTarget());
- final WindowContainer<?> container = displayId == INVALID_DISPLAY
- ? mRoot : mRoot.getDisplayContent(displayId);
- if (container == null) {
- // The waiting container doesn't exist, no need to wait to run the callback. Run and
- // return;
- message.sendToTarget();
- return;
- }
boolean allWindowsDrawn = false;
synchronized (mGlobalLock) {
- if (displayId == INVALID_DISPLAY
- && mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
- // Use the ready-to-play of transition as the signal.
- return;
- }
- container.waitForAllWindowsDrawn();
- mWindowPlacerLocked.requestTraversal();
- mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
- if (container.mWaitingForDrawn.isEmpty()) {
- allWindowsDrawn = true;
- } else {
- if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
- for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
- traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
- }
- }
-
- mWaitingForDrawnCallbacks.put(container, message);
- mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
- checkDrawnWindowsLocked();
- }
+ allWindowsDrawn = waitForAllWindowsDrawnLocked(message, timeout, displayId);
}
if (allWindowsDrawn) {
message.sendToTarget();
}
}
+ /** Return {@code true} if all windows have been drawn. */
+ private boolean waitForAllWindowsDrawnLocked(Message message, long timeout, int displayId) {
+ final WindowContainer<?> container = displayId == INVALID_DISPLAY
+ ? mRoot : mRoot.getDisplayContent(displayId);
+ if (container == null) {
+ // The waiting container doesn't exist, no need to wait. Treat as drawn.
+ return true;
+ }
+ if (displayId == INVALID_DISPLAY
+ && mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
+ // Use the ready-to-play of transition as the signal.
+ return false;
+ }
+ container.waitForAllWindowsDrawn();
+ mWindowPlacerLocked.requestTraversal();
+ mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
+ if (container.mWaitingForDrawn.isEmpty()) {
+ return true;
+ }
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+ traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+ }
+ }
+
+ mWaitingForDrawnCallbacks.put(container, message);
+ mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
+ checkDrawnWindowsLocked();
+ return false;
+ }
+
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
WindowManagerService.this.setForcedDisplaySize(displayId, width, height);
@@ -9010,16 +9020,19 @@ public class WindowManagerService extends IWindowManager.Stub
clearPointerDownOutsideFocusRunnable();
+ final InputTarget focusedInputTarget = mFocusedInputTarget;
if (shouldDelayTouchOutside(t)) {
- mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mPointerDownOutsideFocusRunnable =
+ () -> handlePointerDownOutsideFocus(t, focusedInputTarget);
mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
} else if (!fromHandler) {
// Still post the runnable to handler thread in case there is already a runnable
// in execution, but still waiting to hold the wm lock.
- mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mPointerDownOutsideFocusRunnable =
+ () -> handlePointerDownOutsideFocus(t, focusedInputTarget);
mH.post(mPointerDownOutsideFocusRunnable);
} else {
- handlePointerDownOutsideFocus(t);
+ handlePointerDownOutsideFocus(t, focusedInputTarget);
}
}
@@ -9051,8 +9064,15 @@ public class WindowManagerService extends IWindowManager.Stub
return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform;
}
- private void handlePointerDownOutsideFocus(InputTarget t) {
+ private void handlePointerDownOutsideFocus(InputTarget t, InputTarget focusedInputTarget) {
synchronized (mGlobalLock) {
+ if (mFocusedInputTarget != focusedInputTarget) {
+ // Skip if the mFocusedInputTarget is already changed. This is possible if the
+ // pointer-down-outside-focus event is delayed to be handled.
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+ "Skip onPointerDownOutsideFocusLocked due to input target changed %s", t);
+ return;
+ }
if (mPointerDownOutsideFocusRunnable != null
&& mH.hasCallbacks(mPointerDownOutsideFocusRunnable)) {
// Skip if there's another pending pointer-down-outside-focus event.
@@ -9217,6 +9237,25 @@ public class WindowManagerService extends IWindowManager.Stub
+ "' because it isn't a trusted overlay");
return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
}
+
+ // You need OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission to be able
+ // to set INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS.
+ if (overridePowerKeyBehaviorInFocusedWindow()
+ && (inputFeatures
+ & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
+ != 0) {
+ final int powerPermissionResult =
+ mContext.checkPermission(
+ permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ callingPid,
+ callingUid);
+ if (powerPermissionResult != PackageManager.PERMISSION_GRANTED) {
+ throw new IllegalArgumentException(
+ "Cannot use INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS from" + windowName
+ + " because it doesn't have the"
+ + " OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission");
+ }
+ }
return inputFeatures;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c42aa37d847b..66921ff3adeb 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1335,11 +1335,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
- Task pipTask = container.asTask();
- if (pipTask == null) {
+ TaskFragment pipTaskFragment = container.asTaskFragment();
+ if (pipTaskFragment == null) {
break;
}
- ActivityRecord pipActivity = pipTask.getActivity(
+ ActivityRecord pipActivity = pipTaskFragment.getActivity(
(activity) -> activity.pictureInPictureArgs != null);
if (pipActivity.isState(RESUMED)) {
@@ -1851,14 +1851,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyKeyguardState(@NonNull WindowContainerTransaction.HierarchyOp hop) {
- int effects = TRANSACT_EFFECTS_NONE;
+ int effects = TRANSACT_EFFECTS_LIFECYCLE;
final KeyguardState keyguardState = hop.getKeyguardState();
if (keyguardState != null) {
- int displayId = keyguardState.getDisplayId();
boolean keyguardShowing = keyguardState.getKeyguardShowing();
boolean aodShowing = keyguardState.getAodShowing();
- mService.mKeyguardController.setKeyguardShown(displayId, keyguardShowing, aodShowing);
+ mService.setLockScreenShown(keyguardShowing, aodShowing);
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 82947377d01e..cebe790bb1b9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2757,7 +2757,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* Expands the given rectangle by the region of window resize handle for freeform window.
* @param inOutRect The rectangle to update.
*/
- private void adjustRegionInFreefromWindowMode(Rect inOutRect) {
+ private void adjustRegionInFreeformWindowMode(Rect inOutRect) {
if (!inFreeformWindowingMode()) {
return;
}
@@ -2808,7 +2808,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
}
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
outRegion.set(mTmpRect);
cropRegionToRootTaskBoundsIfNeeded(outRegion);
}
@@ -3608,7 +3608,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
rootTask.getDimBounds(mTmpRect);
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
region.op(mTmpRect, Region.Op.INTERSECT);
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 004f406035c0..832295a7e031 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -629,7 +629,7 @@ class WindowToken extends WindowContainer<WindowState> {
.build();
t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
t.reparent(getSurfaceControl(), leash);
- getPendingTransaction().setFixedTransformHint(leash,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), leash,
getWindowConfiguration().getDisplayRotation());
mFixedRotationTransformLeash = leash;
updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dece612c9424..04642302ce45 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -68,6 +68,7 @@
#include <map>
#include <vector>
+#include "android_hardware_display_DisplayTopology.h"
#include "android_hardware_display_DisplayViewport.h"
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_hardware_input_InputWindowHandle.h"
@@ -321,6 +322,8 @@ public:
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
+ void setDisplayTopology(JNIEnv* env, jobject topologyGraph);
+
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
@@ -348,6 +351,7 @@ public:
void setShouldNotifyTouchpadHardwareState(bool enabled);
void setTouchpadRightClickZoneEnabled(bool enabled);
void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
+ void setTouchpadSystemGesturesEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -361,7 +365,7 @@ public:
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
+ vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId);
void setStylusPointerIconEnabled(bool enabled);
void setInputMethodConnectionIsActive(bool isActive);
void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
@@ -440,7 +444,7 @@ public:
std::shared_ptr<PointerControllerInterface> createPointerController(
PointerControllerInterface::ControllerType type) override;
void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
- const FloatPoint& position) override;
+ const vec2& position) override;
void notifyMouseCursorFadedOnTyping() override;
/* --- InputFilterPolicyInterface implementation --- */
@@ -518,6 +522,9 @@ private:
// middle-click.
bool touchpadThreeFingerTapShortcutEnabled{false};
+ // True to enable system gestures (three- and four-finger swipes) on touchpads.
+ bool touchpadSystemGesturesEnabled{true};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -636,6 +643,11 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
+ android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ // TODO(b/367661489): Use the topology
+}
+
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
const std::string& name) {
ATRACE_CALL();
@@ -790,6 +802,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
outConfig->touchpadThreeFingerTapShortcutEnabled =
mLocked.touchpadThreeFingerTapShortcutEnabled;
+ outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -866,7 +879,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon
}
void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
- const FloatPoint& position) {
+ const vec2& position) {
// Notify the Reader so that devices can be reconfigured.
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1528,6 +1541,22 @@ void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled)
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadSystemGesturesEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadSystemGesturesEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad system gestures enabled to %s.", toString(enabled));
+ mLocked.touchpadSystemGesturesEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2002,7 +2031,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
+vec2 NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
@@ -2071,6 +2100,12 @@ static void nativeSetDisplayViewports(JNIEnv* env, jobject nativeImplObj,
im->setDisplayViewports(env, viewportObjArray);
}
+static void nativeSetDisplayTopology(JNIEnv* env, jobject nativeImplObj,
+ jobject displayTopologyObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setDisplayTopology(env, displayTopologyObj);
+}
+
static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sourceMask, jint scanCode) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2481,6 +2516,13 @@ static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject
getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
}
+static void nativeSetTouchpadSystemGesturesEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadSystemGesturesEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3120,6 +3162,8 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"start", "()V", (void*)nativeStart},
{"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
+ {"setDisplayTopology", "(Landroid/hardware/display/DisplayTopologyGraph;)V",
+ (void*)nativeSetDisplayTopology},
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
@@ -3169,6 +3213,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
{"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
(void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
+ {"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3ef037..09fd8d4ac02e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,6 +49,7 @@ int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_hardware_display_DisplayTopology(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_Freezer(JNIEnv* env);
@@ -114,6 +115,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_hardware_display_DisplayTopology(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_Freezer(env);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index e73dacbed9a3..0c9a89bb0a30 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
+import android.credentials.flags.Flags;
import android.credentials.selection.DisabledProviderData;
import android.credentials.selection.IntentCreationResult;
import android.credentials.selection.IntentFactory;
@@ -46,6 +47,12 @@ import java.util.UUID;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
+
+ private static final String SESSION_ID_TRACK_ONE =
+ "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_ONE";
+ private static final String SESSION_ID_TRACK_TWO =
+ "com.android.server.credentials.CredentialManagerUi.SESSION_ID_TRACK_TWO";
+
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull
@@ -148,8 +155,8 @@ public class CredentialManagerUi {
* by the calling app process. The bottom-sheet navigates to the default page when the intent
* is invoked.
*
- * @param requestInfo the information about the request
- * @param providerDataList the list of provider data from remote providers
+ * @param requestInfo the information about the request
+ * @param providerDataList the list of provider data from remote providers
*/
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
@@ -175,6 +182,11 @@ public class CredentialManagerUi {
mContext, intentCreationResult, mUserId);
Intent intent = intentCreationResult.getIntent();
intent.setAction(UUID.randomUUID().toString());
+ if (Flags.frameworkSessionIdMetricBundle()) {
+ intent.putExtra(SESSION_ID_TRACK_ONE,
+ requestSessionMetric.getInitialPhaseMetric().getSessionIdCaller());
+ intent.putExtra(SESSION_ID_TRACK_TWO, requestSessionMetric.getSessionIdTrackTwo());
+ }
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
return PendingIntent.getActivityAsUser(
@@ -192,8 +204,8 @@ public class CredentialManagerUi {
* each autofill id and passed in as extras in the pending intent set as authentication
* of the pinned entry.
*
- * @param requestInfo the information about the request
- * @param requestSessionMetric the metric object for logging
+ * @param requestInfo the information about the request
+ * @param requestSessionMetric the metric object for logging
*/
public Intent createIntentForAutofill(RequestInfo requestInfo,
RequestSessionMetric requestSessionMetric) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cb333f02757e..1c8d06e52329 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -152,19 +152,20 @@ final class DevicePolicyEngine {
mAdminPolicySize = new SparseArray<>();
}
- private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+ private void forceEnforcementRefreshIfUserRestrictionLocked(
+ @NonNull PolicyDefinition<?> policyDefinition) {
try {
- if (shouldForceEnforcementRefresh(policyDefinition)) {
+ if (isUserRestrictionPolicy(policyDefinition)) {
// This is okay because it's only true for user restrictions which are all <Boolean>
forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
}
} catch (Throwable e) {
// Catch any possible exceptions just to be on the safe side
- Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+ Log.e(TAG, "Exception thrown during forceEnforcementRefreshIfUserRestrictionLocked", e);
}
}
- private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+ private boolean isUserRestrictionPolicy(@NonNull PolicyDefinition<?> policyDefinition) {
// These are all "not nullable" but for the purposes of maximum safety for a lightly tested
// change we check here
if (policyDefinition == null) {
@@ -257,7 +258,7 @@ final class DevicePolicyEngine {
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
@@ -347,7 +348,7 @@ final class DevicePolicyEngine {
Objects.requireNonNull(enforcingAdmin);
synchronized (mLock) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (!hasLocalPolicyLocked(policyDefinition, userId)) {
return;
}
@@ -517,7 +518,7 @@ final class DevicePolicyEngine {
// No need to notify admins as no new policy is actually enforced, we're just filling in
// the data structures.
if (!skipEnforcePolicy) {
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
@@ -570,7 +571,7 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
- maybeForceEnforcementRefreshLocked(policyDefinition);
+ forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
if (policyChanged) {
onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5bd70ef57fac..2627895b8c63 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -26,6 +26,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
@@ -117,6 +118,7 @@ import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
@@ -125,6 +127,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEV
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
@@ -331,6 +334,7 @@ import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyDrawableResource;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.AppFunctionsPolicy;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicyManager.OperationSafetyReason;
@@ -578,6 +582,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -591,6 +596,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Implementation of the device policy APIs.
@@ -9082,9 +9088,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
CallerIdentity caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ if (Flags.setAutoTimeEnabledCoexistence()) {
+ Preconditions.checkCallAuthorization(hasPermission(SET_TIME, callerPackageName));
+ } else {
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -9142,7 +9152,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* who */ null,
SET_TIME,
- caller.getPackageName(),
+ callerPackageName,
UserHandle.USER_ALL
);
Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
@@ -9161,7 +9171,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
CallerIdentity caller = getCallerIdentity(who);
-
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
@@ -9186,10 +9195,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
CallerIdentity caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ if (Flags.setAutoTimeZoneEnabledCoexistence()) {
+ Preconditions.checkCallAuthorization(
+ hasPermission(SET_TIME_ZONE, callerPackageName));
+ } else {
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -9241,7 +9255,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* who */ null,
SET_TIME_ZONE,
- caller.getPackageName(),
+ callerPackageName,
UserHandle.USER_ALL
);
Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
@@ -16234,6 +16248,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
admin.info.getComponent());
return result;
+ } else if (android.security.Flags.aapmApi()) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ return result;
}
return null;
} finally {
@@ -16243,6 +16261,54 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
+ private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId,
+ String identifier) {
+ Objects.requireNonNull(identifier);
+
+ Set<EnforcingAdmin> admins = getEnforcingAdminsForIdentifier(userId, identifier);
+ if (admins.isEmpty()) {
+ return null;
+ }
+
+ final EnforcingAdmin admin;
+ if (admins.size() == 1) {
+ admin = admins.iterator().next();
+ } else {
+ Optional<EnforcingAdmin> dpc = admins.stream()
+ .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
+ admin = dpc.orElseGet(() -> admins.stream().findFirst().get());
+ }
+ return admin == null ? null : admin.getParcelableAdmin();
+ }
+
+ private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) {
+ // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as
+ // before the bug fix for b/192245204.
+ if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(identifier)) {
+ EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+ return admin == null ? Collections.emptySet() : Collections.singleton(admin);
+ }
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ final PolicyDefinition<V> policyDefinition = getPolicyDefinitionForIdentifier(
+ identifier);
+ V value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+ if (value == null) {
+ return Collections.emptySet();
+ }
+ return Stream.concat(mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition)
+ .entrySet().stream(),
+ mDevicePolicyEngine.getLocalPoliciesSetByAdmins(policyDefinition,
+ userId).entrySet().stream())
+ .filter(entry -> value.equals(entry.getValue().getValue()))
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toSet());
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+
/**
* @param restriction The restriction enforced by admin. It could be any user restriction or
* policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA},
@@ -16257,20 +16323,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// before the bug fix for b/192245204.
if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(
restriction)) {
- ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- if (profileOwner != null) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- profileOwner, userId);
- admins.add(admin.getParcelableAdmin());
- return admins;
- }
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null && deviceOwner.first == userId) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- deviceOwner.second, deviceOwner.first);
+ EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+ if (admin != null) {
admins.add(admin.getParcelableAdmin());
- return admins;
}
} else {
long ident = mInjector.binderClearCallingIdentity();
@@ -16319,6 +16374,29 @@ 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)) {
+ return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
+ } else {
+ return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
+ }
+ }
+
+ private EnforcingAdmin getProfileOrDeviceOwnerEnforcingAdmin(int userId) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(profileOwner, userId);
+ }
+ final Pair<Integer, ComponentName> deviceOwner = mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(deviceOwner.second,
+ deviceOwner.first);
+ }
+ return null;
+ }
+
private static String userRestrictionSourceToString(@UserRestrictionSource int source) {
return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source);
}
@@ -16336,6 +16414,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public android.app.admin.EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return getEnforcingAdminInternal(userId, identifier);
+ }
+
+ @Override
public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction(
int userId, String restriction) {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
@@ -22893,6 +22977,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
@@ -22980,6 +23065,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
MANAGE_DEVICE_POLICY_AUTOFILL,
@@ -23178,6 +23264,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ if (Flags.lockNowCoexistence()) {
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ }
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
@@ -23225,6 +23315,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
MANAGE_DEVICE_POLICY_ACROSS_USERS);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS);
// These permissions may grant access to user data and therefore must be protected with
// MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
@@ -23252,8 +23344,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ if (!Flags.lockNowCoexistence()) {
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ }
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
@@ -23888,6 +23982,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public void setAppFunctionsPolicy(String callerPackageName, @AppFunctionsPolicy int policy) {
+ if (!android.app.appfunctions.flags.Flags.enableAppFunctionManager()) {
+ return;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ int userId = caller.getUserId();
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APP_FUNCTIONS_POLICY);
+ EnforcingAdmin enforcingAdmin =
+ enforcePermissionAndGetEnforcingAdmin(
+ /* who */null, MANAGE_DEVICE_POLICY_APP_FUNCTIONS,
+ callerPackageName, userId);
+
+ if (policy == APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.APP_FUNCTIONS, enforcingAdmin, userId);
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.APP_FUNCTIONS,
+ enforcingAdmin, new IntegerPolicyValue(policy),
+ userId);
+ }
+ }
+
+ @Override
+ public @AppFunctionsPolicy int getAppFunctionsPolicy(String callerPackageName, int userId) {
+ if (!android.app.appfunctions.flags.Flags.enableAppFunctionManager()) {
+ return APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforceCanQuery(MANAGE_DEVICE_POLICY_APP_FUNCTIONS, callerPackageName, userId);
+ Integer policy =
+ mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.APP_FUNCTIONS, userId);
+ if (policy == null) {
+ return APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY;
+ }
+ return policy;
+ }
+
private void updateContentProtectionPolicyCache(@UserIdInt int userId) {
mPolicyCache.setContentProtectionPolicy(
userId,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 1fd628a20afa..5a0b079b6a24 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -321,8 +321,10 @@ final class EnforcingAdmin {
authority = DpcAuthority.DPC_AUTHORITY;
} else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
+ } else if (mIsSystemAuthority) {
+ // For now, System Authority returns UnknownAuthority.
+ authority = new UnknownAuthority(mSystemEntity);
} else {
- // For now, System Authority returns UNKNOWN_AUTHORITY.
authority = UnknownAuthority.UNKNOWN_AUTHORITY;
}
return new android.app.admin.EnforcingAdmin(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
index 40cf0e979375..c8c953d53f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.content.pm.PackageManagerInternal;
import android.util.ArraySet;
@@ -64,7 +65,7 @@ public class PackageSuspender {
/**
* Suspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to suspend but could not be
+ * @return an array of packages that the admin has requested to suspend but could not be
* suspended, due to DPM and PackageManager exemption list.
*
*/
@@ -87,7 +88,7 @@ public class PackageSuspender {
/**
* Suspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be suspended, either due to the exemption list,
+ * @return the set of packages that couldn't be suspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> suspendWithExemption(Set<String> packages) {
@@ -112,15 +113,15 @@ public class PackageSuspender {
/**
* Unsuspend packages that are requested by a single admin
*
- * @return a list of packages that the admin has requested to unsuspend but could not be
- * unsuspended, due to other amdin's policy or PackageManager restriction.
+ * @return an array of packages that the admin has requested to unsuspend but could not be
+ * unsuspended, due to other admin's policy or PackageManager restriction.
*
*/
public String[] unsuspend(Set<String> packages) {
- // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved
- // suspended packages list and not what the admin has requested. This is because some
- // packages might still be subject to another admin's suspension request.
- Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore);
+ // Unlike suspend(), when unsuspending, take suspension by other admins into account: only
+ // packages not suspended by other admins are passed to PackageManager.
+ Set<String> packagesToUnsuspend = new ArraySet<>(
+ Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore);
packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
// To calculate the result (which packages are not unsuspended), start with packages that
@@ -139,7 +140,7 @@ public class PackageSuspender {
/**
* Unsuspend packages considering the exemption list.
*
- * @return the list of packages that couldn't be unsuspended, either due to the exemption list,
+ * @return the set of packages that couldn't be unsuspended, either due to the exemption list,
* or due to failures from PackageManagerInternal itself.
*/
private Set<String> unsuspendWithExemption(Set<String> packages) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 24b16b7c2c60..543e32fae55f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -318,6 +318,20 @@ final class PolicyDefinition<V> {
PolicyEnforcerCallbacks::setContentProtectionPolicy,
new IntegerPolicySerializer());
+ static PolicyDefinition<Integer> APP_FUNCTIONS = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.APP_FUNCTIONS_POLICY),
+ new MostRestrictive<>(
+ List.of(
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_DISABLED),
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE),
+ new IntegerPolicyValue(
+ DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY))),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ PolicyEnforcerCallbacks::noOp,
+ new IntegerPolicySerializer());
+
static PolicyDefinition<Integer> PASSWORD_COMPLEXITY = new PolicyDefinition<>(
new NoArgsPolicyKey(DevicePolicyIdentifiers.PASSWORD_COMPLEXITY_POLICY),
new MostRestrictive<>(
@@ -398,6 +412,8 @@ final class PolicyDefinition<V> {
USB_DATA_SIGNALING);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
CONTENT_PROTECTION);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.APP_FUNCTIONS_POLICY,
+ APP_FUNCTIONS);
// Intentionally not flagged since if the flag is flipped off on a device already
// having PASSWORD_COMPLEXITY policy in the on-device XML, it will cause the
// deserialization logic to break due to seeing an unknown tag.
@@ -656,10 +672,6 @@ final class PolicyDefinition<V> {
}
}
- void saveToXml(TypedXmlSerializer serializer) throws IOException {
- mPolicyKey.saveToXml(serializer);
- }
-
@Nullable
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index a4fa0892da61..0d9dbaaec6b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,7 +19,6 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.app.admin.flags.Flags;
import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -41,7 +40,6 @@ final class PolicyState<V> {
private static final String TAG = "PolicyState";
private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
@@ -225,12 +223,6 @@ final class PolicyState<V> {
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- if (!Flags.dontWritePolicyDefinition()) {
- serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- mPolicyDefinition.saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- }
-
if (mCurrentResolvedPolicy != null) {
serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 112414e6c4df..65315af45486 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.media.tv.flags.Flags.mediaQualityFw;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -200,7 +201,6 @@ import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
-import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
@@ -391,6 +391,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
"com.android.server.adservices.AdServicesManagerService$Lifecycle";
+ private static final String ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS =
+ "com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService";
private static final String ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS =
"com.android.server.ondevicepersonalization."
+ "OnDevicePersonalizationSystemService$Lifecycle";
@@ -428,6 +430,8 @@ public final class SystemServer implements Dumpable {
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
"com.android.server.ConnectivityServiceInitializer";
+ private static final String CONNECTIVITY_SERVICE_INITIALIZER_B_CLASS =
+ "com.android.server.ConnectivityServiceInitializerB";
private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
"com.android.server.NetworkStatsServiceInitializer";
private static final String UWB_APEX_SERVICE_JAR_PATH =
@@ -1485,7 +1489,6 @@ public final class SystemServer implements Dumpable {
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
VpnManagerService vpnManager = null;
- VcnManagementService vcnManagement = null;
NetworkPolicyManagerService networkPolicy = null;
WindowManagerService wm = null;
NetworkTimeUpdateService networkTimeUpdater = null;
@@ -2231,8 +2234,10 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartVcnManagementService");
try {
- vcnManagement = VcnManagementService.create(context);
- ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+ // TODO: b/375213246 When VCN is in mainline module, load it from the apex path.
+ // Whether VCN will be in apex or in the platform will be gated by a build system
+ // flag.
+ mSystemServiceManager.startService(CONNECTIVITY_SERVICE_INITIALIZER_B_CLASS);
} catch (Throwable e) {
reportWtf("starting VCN Management Service", e);
}
@@ -2616,9 +2621,11 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
- t.traceBegin("StartMediaQuality");
- mSystemServiceManager.startService(MediaQualityService.class);
- t.traceEnd();
+ if (mediaQualityFw() && isTv) {
+ t.traceBegin("StartMediaQuality");
+ mSystemServiceManager.startService(MediaQualityService.class);
+ t.traceEnd();
+ }
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
t.traceBegin("StartMediaResourceMonitor");
@@ -3156,7 +3163,6 @@ public final class SystemServer implements Dumpable {
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
final VpnManagerService vpnManagerF = vpnManager;
- final VcnManagementService vcnManagementF = vcnManagement;
final WindowManagerService windowManagerF = wm;
final ConnectivityManager connectivityF = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -3283,15 +3289,6 @@ public final class SystemServer implements Dumpable {
reportWtf("making VpnManagerService ready", e);
}
t.traceEnd();
- t.traceBegin("MakeVcnManagementServiceReady");
- try {
- if (vcnManagementF != null) {
- vcnManagementF.systemReady();
- }
- } catch (Throwable e) {
- reportWtf("making VcnManagementService ready", e);
- }
- t.traceEnd();
t.traceBegin("MakeNetworkPolicyServiceReady");
try {
if (networkPolicyF != null) {
@@ -3457,7 +3454,7 @@ public final class SystemServer implements Dumpable {
private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
t.traceBegin("startOnDeviceIntelligenceManagerService");
- mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ mSystemServiceManager.startService(ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS);
t.traceEnd();
}
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 4412968999e5..0d222fb4409e 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -10,14 +10,6 @@ flag {
}
flag {
- name: "remove_java_service_manager_cache"
- namespace: "system_performance"
- description: "This flag turns off Java's Service Manager caching mechanism."
- bug: "333854840"
- is_fixed_read_only: true
-}
-
-flag {
name: "remove_text_service"
namespace: "wear_frameworks"
description: "Remove TextServiceManagerService on Wear"
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 114fe324f016..945720544991 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
+ <hal format="aidl">
+ <name>android.frameworks.devicestate</name>
+ <version>1</version>
+ <fqname>IDeviceStateService/default</fqname>
+ </hal>
</manifest>
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index f5360eb9a56a..6b28047c2610 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -213,7 +213,8 @@ class ShareTargetPredictor extends AppTargetPredictor {
}
private int getShareEventType(IntentFilter intentFilter) {
- String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
+ String mimeType = (intentFilter != null && intentFilter.countDataTypes() > 0)
+ ? intentFilter.getDataType(0) : null;
return getDataManager().mimeTypeToShareEventType(mimeType);
}
diff --git a/services/proguard.flags b/services/proguard.flags
index 977bd19a7236..0e1f68e03d7d 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -44,6 +44,9 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; }
+# allow invoking start-service using class name in both apex and services jar.
+-keep,allowoptimization,allowaccessmodification class com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService { *; }
+
# Keep all aconfig Flag class as they might be statically referenced by other packages
# An merge or inlining could lead to missing dependencies that cause run time errors
-keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; }
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 02e0bbfd3519..eb61a40e0ba5 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -30,13 +30,10 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;
import android.app.backup.BackupManager;
@@ -90,7 +87,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -110,7 +106,6 @@ public class UserBackupManagerServiceTest {
private static final String TAG = "BMSTest";
private static final String PACKAGE_1 = "some.package.1";
private static final String PACKAGE_2 = "some.package.2";
- private static final String USER_FACING_PACKAGE = "user.facing.package";
private static final int USER_ID = 10;
@Mock private TransportManager mTransportManager;
@@ -1213,47 +1208,6 @@ public class UserBackupManagerServiceTest {
eq(packageTrackingReceiver), eq(UserHandle.of(USER_ID)), any(), any(), any());
}
- @Test
- public void testFilterUserFacingPackages_shouldSkipUserFacing_filtersUserFacing() {
- List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
- getPackageInfo(PACKAGE_1));
- UserBackupManagerService backupManagerService = spy(
- createUserBackupManagerServiceAndRunTasks());
- when(backupManagerService.shouldSkipUserFacingData()).thenReturn(true);
- when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
- List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
- packages);
-
- assertFalse(containsPackage(filteredPackages, USER_FACING_PACKAGE));
- assertTrue(containsPackage(filteredPackages, PACKAGE_1));
- }
-
- @Test
- public void testFilterUserFacingPackages_shouldNotSkipUserFacing_doesNotFilterUserFacing() {
- List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
- getPackageInfo(PACKAGE_1));
- UserBackupManagerService backupManagerService = spy(
- createUserBackupManagerServiceAndRunTasks());
- when(backupManagerService.shouldSkipUserFacingData()).thenReturn(false);
- when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
- List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
- packages);
-
- assertTrue(containsPackage(filteredPackages, USER_FACING_PACKAGE));
- assertTrue(containsPackage(filteredPackages, PACKAGE_1));
- }
-
- private static boolean containsPackage(List<PackageInfo> packages, String targetPackage) {
- for (PackageInfo packageInfo : packages) {
- if (targetPackage.equals(packageInfo.packageName)) {
- return true;
- }
- }
- return false;
- }
-
private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() {
return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
USER_ID, mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager);
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 aeb1ba93f049..de16b7ee8126 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
@@ -865,7 +865,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(mBackupManagerService).setWorkSource(null);
- verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+ eq(false));
}
@Test
@@ -1101,7 +1102,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(agentMock.agentBinder).fail(any());
- verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+ eq(false));
}
@Test
@@ -1423,7 +1425,7 @@ public class KeyValueBackupTaskTest {
assertCleansUpFiles(mTransport, PM_PACKAGE);
// We don't unbind PM
verify(mBackupAgentConnectionManager, never()).unbindAgent(
- argThat(applicationInfo(PM_PACKAGE)));
+ argThat(applicationInfo(PM_PACKAGE)), eq(false));
}
@Test
@@ -1445,7 +1447,7 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(mBackupAgentConnectionManager, never()).unbindAgent(
- argThat(applicationInfo(PM_PACKAGE)));
+ argThat(applicationInfo(PM_PACKAGE)), eq(false));
}
@Test
@@ -1651,7 +1653,7 @@ public class KeyValueBackupTaskTest {
InOrder inOrder = inOrder(agentMock.agent, mBackupAgentConnectionManager);
inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L));
inOrder.verify(mBackupAgentConnectionManager).unbindAgent(
- argThat(applicationInfo(PACKAGE_1)));
+ argThat(applicationInfo(PACKAGE_1)), eq(false));
}
@Test
@@ -2983,7 +2985,8 @@ public class KeyValueBackupTaskTest {
private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) {
assertCleansUpFiles(transport, packageData);
- verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)),
+ eq(false));
}
private void assertCleansUpFiles(TransportData transport, PackageData packageData) {
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 53a25dd454ef..0ccaa6043f5f 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -19,20 +19,28 @@ package com.android.server.supervision;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
import android.app.supervision.SupervisionManagerInternal;
+import android.app.supervision.flags.Flags;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.UserHandle;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
@@ -42,6 +50,7 @@ import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/** Service for handling system supervision. */
public class SupervisionService extends ISupervisionManager.Stub {
@@ -56,24 +65,17 @@ public class SupervisionService extends ISupervisionManager.Stub {
private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
private final DevicePolicyManagerInternal mDpmInternal;
+ private final PackageManager mPackageManager;
private final UserManagerInternal mUserManagerInternal;
public SupervisionService(Context context) {
mContext = context.createAttributionContext(LOG_TAG);
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+ mPackageManager = context.getPackageManager();
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
}
- void syncStateWithDevicePolicyManager(TargetUser user) {
- if (user.isPreCreated()) return;
-
- // Ensure that supervision is enabled when supervision app is the profile owner.
- if (android.app.admin.flags.Flags.enableSupervisionServiceSync() && isProfileOwner(user)) {
- setSupervisionEnabledForUser(user.getUserIdentifier(), true);
- }
- }
-
@Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
synchronized (getLockObject()) {
@@ -103,7 +105,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
pw.println("SupervisionService state:");
pw.increaseIndent();
- var users = mUserManagerInternal.getUsers(false);
+ List<UserInfo> users = mUserManagerInternal.getUsers(false);
synchronized (getLockObject()) {
for (var user : users) {
getUserDataLocked(user.id).dump(pw);
@@ -135,15 +137,28 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
- /** Returns whether the supervision app has profile owner status. */
- private boolean isProfileOwner(TargetUser user) {
- ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(user.getUserIdentifier());
- if (profileOwner == null) {
- return false;
+ /** Ensures that supervision is enabled when supervision app is the profile owner. */
+ private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
+ if (isProfileOwner(userId)) {
+ setSupervisionEnabledForUser(userId, true);
+ } else {
+ // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
+ // This might only be possible after introducing specific and public APIs to enable
+ // supervision.
+ setSupervisionEnabledForUser(userId, false);
}
+ }
- String configPackage = mContext.getResources().getString(R.string.config_systemSupervision);
- return profileOwner.getPackageName().equals(configPackage);
+ /** Returns whether the supervision app has profile owner status. */
+ private boolean isProfileOwner(@UserIdInt int userId) {
+ ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
+ return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
+ }
+
+ /** Returns whether the given package name belongs to the supervision role holder. */
+ private boolean isSupervisionAppPackage(String packageName) {
+ return packageName.equals(
+ mContext.getResources().getString(R.string.config_systemSupervision));
}
public static class Lifecycle extends SystemService {
@@ -154,15 +169,46 @@ public class SupervisionService extends ISupervisionManager.Stub {
mSupervisionService = new SupervisionService(context);
}
+ @VisibleForTesting
+ Lifecycle(Context context, SupervisionService supervisionService) {
+ super(context);
+ mSupervisionService = supervisionService;
+ }
+
@Override
public void onStart() {
publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
+ if (Flags.enableSyncWithDpm()) {
+ registerProfileOwnerListener();
+ }
+ }
+
+ @VisibleForTesting
+ void registerProfileOwnerListener() {
+ IntentFilter poIntentFilter = new IntentFilter();
+ poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
+ poIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ getContext()
+ .registerReceiverForAllUsers(
+ new ProfileOwnerBroadcastReceiver(),
+ poIntentFilter,
+ /* brodcastPermission= */ null,
+ /* scheduler= */ null);
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
- mSupervisionService.syncStateWithDevicePolicyManager(user);
+ if (Flags.enableSyncWithDpm() && !user.isPreCreated()) {
+ mSupervisionService.syncStateWithDevicePolicyManager(user.getUserIdentifier());
+ }
+ }
+
+ private final class ProfileOwnerBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSupervisionService.syncStateWithDevicePolicyManager(getSendingUserId());
+ }
}
}
@@ -170,6 +216,21 @@ public class SupervisionService extends ISupervisionManager.Stub {
private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
@Override
+ public boolean isActiveSupervisionApp(int uid) {
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null) {
+ return false;
+ }
+ for (var packageName : packages) {
+ if (SupervisionService.this.isSupervisionAppPackage(packageName)) {
+ int userId = UserHandle.getUserId(uid);
+ return SupervisionService.this.isSupervisionEnabledForUser(userId);
+ }
+ }
+ return false;
+ }
+
+ @Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
return SupervisionService.this.isSupervisionEnabledForUser(userId);
}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
index 5492ba6b9dd1..6e14bad11837 100644
--- a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.MethodDescriptorParser;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -37,7 +38,7 @@ import java.lang.reflect.Method;
/**
* Test class for
- * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader,
+ * {@link MethodDescriptorParser#parseMethodDescriptor(ClassLoader,
* MethodDescriptor)}.
* <p>
* Build/Install/Run:
@@ -119,13 +120,13 @@ public class ParseMethodDescriptorTest {
}
private Method parseMethodDescriptor(String fqcn, String methodName) {
- return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ return MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(),
getMethodDescriptor(fqcn, methodName, new String[]{}));
}
private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
- return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ return MethodDescriptorParser.parseMethodDescriptor(
getClass().getClassLoader(),
getMethodDescriptor(fqcn, methodName, fqParameters));
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
index a93e8ad93756..97f1bd46678f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -574,57 +574,16 @@ public class PackageVerificationStateTest extends AndroidTestCase {
assertTrue(state.isInstallAllowed());
}
- public void testAreAllVerificationsComplete_onlyVerificationPasses() {
+ public void testAreAllVerificationsComplete() {
PackageVerificationState state = new PackageVerificationState(null);
state.addRequiredVerifierUid(REQUIRED_UID_1);
assertFalse(state.areAllVerificationsComplete());
state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
- assertFalse(state.areAllVerificationsComplete());
- }
-
- public void testAreAllVerificationsComplete_onlyIntegrityCheckPasses() {
- PackageVerificationState state = new PackageVerificationState(null);
- state.addRequiredVerifierUid(REQUIRED_UID_1);
- assertFalse(state.areAllVerificationsComplete());
-
- state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-
- assertFalse(state.areAllVerificationsComplete());
- }
-
- public void testAreAllVerificationsComplete_bothPasses() {
- PackageVerificationState state = new PackageVerificationState(null);
- state.addRequiredVerifierUid(REQUIRED_UID_1);
- assertFalse(state.areAllVerificationsComplete());
-
- state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
-
assertTrue(state.areAllVerificationsComplete());
}
- public void testAreAllVerificationsComplete_onlyVerificationFails() {
- PackageVerificationState state = new PackageVerificationState(null);
- state.addRequiredVerifierUid(REQUIRED_UID_1);
- assertFalse(state.areAllVerificationsComplete());
-
- state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT);
-
- assertFalse(state.areAllVerificationsComplete());
- }
-
- public void testAreAllVerificationsComplete_onlyIntegrityCheckFails() {
- PackageVerificationState state = new PackageVerificationState(null);
- state.addRequiredVerifierUid(REQUIRED_UID_1);
- assertFalse(state.areAllVerificationsComplete());
-
- state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
-
- assertFalse(state.areAllVerificationsComplete());
- }
-
private void processOnTimeout(PackageVerificationState state, int code, int uid) {
// CHECK_PENDING_VERIFICATION handler.
assertFalse("Verification should not be marked as complete yet",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index c1271bb0ee36..9a61492971a5 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -511,7 +511,7 @@ public class ScanTests {
.addUsesPermission(
new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
- final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
+ final ScanResult scanResult = ScanPackageUtils.scanPackageOnly(
createBasicScanRequestBuilder(basicPackage).build(),
mMockInjector,
true /*isUnderFactoryTest*/,
@@ -559,7 +559,7 @@ public class ScanTests {
private ScanResult executeScan(
ScanRequest scanRequest) throws PackageManagerException {
- ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
+ ScanResult result = ScanPackageUtils.scanPackageOnly(
scanRequest,
mMockInjector,
false /*isUnderFactoryTest*/,
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 9772ef929eae..5db6a8f12e52 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -145,6 +145,7 @@ import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.PersistableBundleUtils;
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -179,7 +180,6 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
import com.android.server.VpnTestBase;
-import com.android.server.vcn.util.PersistableBundleUtils;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
index 44c7dec7633e..cd0bd41d9f05 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -130,34 +130,64 @@ public class BrightnessTrackerTest {
}
@Test
- public void testStartStopTrackerScreenOnOff() {
+ public void testStartStopTrackerScreenStates() {
mInjector.mInteractive = false;
+ mInjector.mDisplayState = Display.STATE_OFF;
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.sendInteractivityChange(true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
+ assertNotNull(mInjector.mDisplayListener);
+
+ mInjector.setDisplayState(Display.STATE_OFF);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
+ mInjector.setDisplayState(Display.STATE_DOZE);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
+ mInjector.setDisplayState(Display.STATE_DOZE_SUSPEND);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
- mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setDisplayState(Display.STATE_ON_SUSPEND);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
- // Turn screen on while brightness mode is manual
+ // Screen on while device is not interactive
+ mInjector.setDisplayState(Display.STATE_ON);
+ mInjector.sendInteractivityChange(false);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
+
+ // Device becomes interactive while brightness mode is manual
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.sendInteractivityChange(true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
// Set brightness mode to automatic while screen is off.
- mInjector.sendScreenChange(/* screenOn= */ false);
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+ mInjector.setDisplayState(Display.STATE_OFF);
+ assertNull(mInjector.mSensorListener);
+ assertFalse(mInjector.mColorSamplingEnabled);
+ assertNotNull(mInjector.mDisplayListener);
+
+ // Set brightness mode to automatic while screen is in doze.
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+ mInjector.setDisplayState(Display.STATE_DOZE);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn on screen while brightness mode is automatic.
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
@@ -166,11 +196,11 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mBroadcastReceiver);
assertFalse(mInjector.mIdleScheduled);
assertFalse(mInjector.mColorSamplingEnabled);
+ assertNull(mInjector.mDisplayListener);
}
@Test
public void testModifyBrightnessConfiguration() {
- mInjector.mInteractive = true;
// Start with tracker not listening for color samples.
startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
assertFalse(mInjector.mColorSamplingEnabled);
@@ -186,13 +216,17 @@ public class BrightnessTrackerTest {
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is off, update config to turn on color sampling.
- mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setDisplayState(Display.STATE_OFF);
mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
+ // Pretend screen is in doze
+ mInjector.setDisplayState(Display.STATE_DOZE);
+ assertFalse(mInjector.mColorSamplingEnabled);
+
// Pretend screen is on.
- mInjector.sendScreenChange(/* screenOn= */ true);
+ mInjector.setDisplayState(Display.STATE_ON);
assertTrue(mInjector.mColorSamplingEnabled);
mTracker.stop();
@@ -208,7 +242,6 @@ public class BrightnessTrackerTest {
mInjector.mDefaultSamplingAttributes.getComponentMask());
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
@@ -220,7 +253,6 @@ public class BrightnessTrackerTest {
0x2);
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
@@ -228,14 +260,12 @@ public class BrightnessTrackerTest {
mInjector.mDefaultSamplingAttributes = null;
startTracker(mTracker);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
}
@Test
public void testColorSampling_FrameRateChange() {
startTracker(mTracker);
assertTrue(mInjector.mColorSamplingEnabled);
- assertNotNull(mInjector.mDisplayListener);
int noFramesSampled = mInjector.mNoColorSamplingFrames;
mInjector.mFrameRate = 120.0f;
// Wrong display
@@ -248,7 +278,6 @@ public class BrightnessTrackerTest {
@Test
public void testAdaptiveOnOff() {
- mInjector.mInteractive = true;
mInjector.mIsBrightnessModeAutomatic = false;
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
@@ -256,7 +285,6 @@ public class BrightnessTrackerTest {
assertNotNull(mInjector.mContentObserver);
assertTrue(mInjector.mIdleScheduled);
assertFalse(mInjector.mColorSamplingEnabled);
- assertNull(mInjector.mDisplayListener);
mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNotNull(mInjector.mSensorListener);
@@ -835,12 +863,14 @@ public class BrightnessTrackerTest {
mInjector.waitForHandler();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mDisplayListener);
// Resetting sensor should start listener again
mTracker.setLightSensor(mLightSensorFake);
mInjector.waitForHandler();
assertNotNull(mInjector.mSensorListener);
assertEquals(mInjector.mLightSensor, mLightSensorFake);
+ assertNotNull(mInjector.mDisplayListener);
Sensor secondSensor = new Sensor(mInputSensorInfoMock);
// Setting a different listener should keep things working
@@ -848,6 +878,7 @@ public class BrightnessTrackerTest {
mInjector.waitForHandler();
assertNotNull(mInjector.mSensorListener);
assertEquals(mInjector.mLightSensor, secondSensor);
+ assertNotNull(mInjector.mDisplayListener);
}
@Test
@@ -862,6 +893,7 @@ public class BrightnessTrackerTest {
startTracker(mTracker);
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mDisplayListener);
}
@Test
@@ -895,6 +927,7 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mContentObserver);
assertNull(mInjector.mBroadcastReceiver);
assertFalse(mInjector.mIdleScheduled);
+ assertNull(mInjector.mDisplayListener);
// mInjector asserts that we aren't removing a null receiver
mTracker.stop();
@@ -1017,6 +1050,7 @@ public class BrightnessTrackerTest {
Handler mHandler;
boolean mIdleScheduled;
boolean mInteractive = true;
+ int mDisplayState = Display.STATE_ON;
int[] mProfiles;
ContentObserver mContentObserver;
boolean mIsBrightnessModeAutomatic = true;
@@ -1042,14 +1076,20 @@ public class BrightnessTrackerTest {
waitForHandler();
}
- void sendScreenChange(boolean screenOn) {
- mInteractive = screenOn;
+ void sendInteractivityChange(boolean interactive) {
+ mInteractive = interactive;
Intent intent = new Intent();
- intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+ intent.setAction(interactive ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
waitForHandler();
}
+ void setDisplayState(int state) {
+ mDisplayState = state;
+ mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+ waitForHandler();
+ }
+
void waitForHandler() {
Idle idle = new Idle();
mHandler.getLooper().getQueue().addIdleHandler(idle);
@@ -1183,6 +1223,11 @@ public class BrightnessTrackerTest {
}
@Override
+ public int getDisplayState(Context context) {
+ return mDisplayState;
+ }
+
+ @Override
public int getNightDisplayColorTemperature(Context context) {
return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
mDefaultNightModeColorTemperature);
@@ -1239,7 +1284,7 @@ public class BrightnessTrackerTest {
}
@Override
- public void unRegisterDisplayListener(Context context,
+ public void unregisterDisplayListener(Context context,
DisplayManager.DisplayListener listener) {
mDisplayListener = null;
}
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 47e96d378149..365cbaed2aac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -123,7 +123,6 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
import android.os.test.FakePermissionEnforcer;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -234,6 +233,7 @@ public class DisplayManagerServiceTest {
private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
+ private static final String TOPOLOGY_CHANGED_EVENT = "TOPOLOGY_CHANGED_EVENT";
@Rule(order = 0)
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -1387,21 +1387,23 @@ public class DisplayManagerServiceTest {
}
/**
- * Tests that it's not allowed to create an auto-mirror virtual display without
- * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays
+ * Tests that it is not allowed to create an auto-mirror virtual display for a virtual device
+ * without ADD_MIRROR_DISPLAY permission / without the mirror display capability.
*/
- @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE)
@Test
- public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException()
- throws Exception {
+ public void createAutoMirrorDisplay_withoutPermission_throwsException() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
registerDefaultDisplays(displayManager);
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
- when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_DENIED);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
PackageManager.PERMISSION_DENIED);
@@ -1432,8 +1434,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
- when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1466,8 +1472,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
- when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -1534,8 +1544,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
- when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
.thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1571,8 +1585,12 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
- when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+ when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ } else {
+ when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+ }
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
// Create an auto-mirror virtual display using a virtual device.
@@ -3699,8 +3717,7 @@ public class DisplayManagerServiceTest {
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3718,8 +3735,7 @@ public class DisplayManagerServiceTest {
public void testGetDisplayTopology_NullIfFlagDisabled() {
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3733,8 +3749,7 @@ public class DisplayManagerServiceTest {
@Test
public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3748,8 +3763,7 @@ public class DisplayManagerServiceTest {
public void testSetDisplayTopology() {
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3762,8 +3776,7 @@ public class DisplayManagerServiceTest {
@Test
public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3774,6 +3787,49 @@ public class DisplayManagerServiceTest {
() -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
}
+ @Test
+ public void testShouldNotifyTopologyChanged() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT);
+ }
+
+ @Test
+ public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ // Only subscribe to display events, not topology events
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ STANDARD_DISPLAY_EVENTS);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).isEmpty();
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -4225,6 +4281,12 @@ public class DisplayManagerServiceTest {
eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
}
+ @Override
+ public void onTopologyChanged(DisplayTopology topology) {
+ mReceivedEvents.add(TOPOLOGY_CHANGED_EVENT);
+ eventSeen(TOPOLOGY_CHANGED_EVENT);
+ }
+
public void clear() {
mReceivedEvents.clear();
}
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 a2d2a81b20b4..5d427139a857 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -20,22 +20,26 @@ import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.view.Display
import android.view.DisplayInfo
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import java.util.function.BooleanSupplier
class DisplayTopologyCoordinatorTest {
private lateinit var coordinator: DisplayTopologyCoordinator
private val displayInfo = DisplayInfo()
+ private val topologyChangeExecutor = Runnable::run
private val mockTopology = mock<DisplayTopology>()
- private val mockIsExtendedDisplayEnabled = mock<BooleanSupplier>()
+ private val mockTopologyCopy = mock<DisplayTopology>()
+ private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
+ private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()
@Before
fun setUp() {
@@ -47,13 +51,14 @@ class DisplayTopologyCoordinatorTest {
val injector = object : DisplayTopologyCoordinator.Injector() {
override fun getTopology() = mockTopology
}
- coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled)
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
+ whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
+ coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
+ mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot())
}
@Test
fun addDisplay() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
-
coordinator.onDisplayAdded(displayInfo)
val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
@@ -61,24 +66,43 @@ class DisplayTopologyCoordinatorTest {
val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
/ displayInfo.logicalDensityDpi)
verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
+ verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
}
@Test
fun addDisplay_extendedDisplaysDisabled() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(false)
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
coordinator.onDisplayAdded(displayInfo)
verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
}
@Test
fun addDisplay_notInDefaultDisplayGroup() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
coordinator.onDisplayAdded(displayInfo)
verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
+ }
+
+ @Test
+ fun getTopology_copy() {
+ assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
+ }
+
+ @Test
+ fun setTopology_normalize() {
+ val topology = mock<DisplayTopology>()
+ val topologyCopy = mock<DisplayTopology>()
+ whenever(topology.copy()).thenReturn(topologyCopy)
+
+ coordinator.topology = topology
+
+ verify(topology).normalize()
+ verify(mockTopologyChangedCallback).invoke(topologyCopy)
}
} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index b002a1f73006..241dc10747ac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+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;
@@ -327,6 +329,23 @@ public class LogicalDisplayTest {
}
@Test
+ public void testBrightnessConfigurationFromDisplayDevice() {
+ mDisplayDeviceInfo.brightnessMinimum = 0.12f;
+ mDisplayDeviceInfo.brightnessDim = 0.34f;
+ mDisplayDeviceInfo.brightnessDefault = 0.56f;
+ mDisplayDeviceInfo.brightnessMaximum = 0.78f;
+
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+ mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+
+ DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
+ assertThat(info.brightnessMinimum).isEqualTo(0.12f);
+ assertThat(info.brightnessDim).isEqualTo(0.34f);
+ assertThat(info.brightnessDefault).isEqualTo(0.56f);
+ assertThat(info.brightnessMaximum).isEqualTo(0.78f);
+ }
+
+ @Test
public void testGetDisplayPosition() {
Point expectedPosition = new Point(0, 0);
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 e0b0fec380dd..dbd5c65f9ba3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+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;
@@ -25,10 +27,12 @@ import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.Process;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
+import android.view.Display;
import android.view.Surface;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -56,6 +60,9 @@ public class VirtualDisplayAdapterTest {
private static final int MAX_DEVICES = 3;
private static final int MAX_DEVICES_PER_PACKAGE = 2;
+ private static final float DEFAULT_BRIGHTNESS = 0.34f;
+ private static final float DIM_BRIGHTNESS = 0.12f;
+
@Rule
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
@@ -123,6 +130,64 @@ public class VirtualDisplayAdapterTest {
}
@Test
+ public void testCreateVirtualDisplay_createDisplayDeviceInfoFromDefaults() {
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+ "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+ .build();
+
+ final String packageName = "testpackage";
+ final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+ packageName, Process.myUid(), config);
+
+ DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+ mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+ packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+ assertNotNull(displayDevice);
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+ assertNotNull(info);
+
+ assertThat(info.width).isEqualTo(640);
+ assertThat(info.height).isEqualTo(480);
+ assertThat(info.densityDpi).isEqualTo(240);
+ assertThat(info.xDpi).isEqualTo(240);
+ assertThat(info.yDpi).isEqualTo(240);
+ assertThat(info.name).isEqualTo("testDisplayName");
+ assertThat(info.uniqueId).isEqualTo(displayUniqueId);
+ assertThat(info.ownerPackageName).isEqualTo(packageName);
+ assertThat(info.ownerUid).isEqualTo(10);
+ assertThat(info.type).isEqualTo(Display.TYPE_VIRTUAL);
+ assertThat(info.brightnessMinimum).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+ assertThat(info.brightnessMaximum).isEqualTo(PowerManager.BRIGHTNESS_MAX);
+ assertThat(info.brightnessDefault).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+ assertThat(info.brightnessDim).isEqualTo(PowerManager.BRIGHTNESS_INVALID);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_createDisplayDeviceInfoFromVirtualDisplayConfig() {
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+ "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+ .setDefaultBrightness(DEFAULT_BRIGHTNESS)
+ .setDimBrightness(DIM_BRIGHTNESS)
+ .build();
+
+ final String packageName = "testpackage";
+ final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+ packageName, Process.myUid(), config);
+
+ DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+ mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+ packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+ assertNotNull(displayDevice);
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+ assertNotNull(info);
+
+ assertThat(info.brightnessDefault).isEqualTo(DEFAULT_BRIGHTNESS);
+ assertThat(info.brightnessDim).isEqualTo(DIM_BRIGHTNESS);
+ }
+
+ @Test
public void testCreatesVirtualDisplay_checkGeneratedDisplayUniqueIdPrefix() {
VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
/* height= */ 1, /* densityDpi= */ 1).build();
@@ -335,10 +400,6 @@ public class VirtualDisplayAdapterTest {
@Override
public void onStopped() {
}
-
- @Override
- public void onRequestedBrightnessChanged(float brightness) {
- }
};
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 2aafdfa8a4d3..238654d2aaf1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -19,13 +19,15 @@ package com.android.server.display.brightness.clamper;
import static android.view.Display.STATE_OFF;
import static android.view.Display.STATE_ON;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,7 +35,6 @@ import android.content.Context;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
-import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.testing.TestableContext;
@@ -83,8 +84,6 @@ public class BrightnessClamperControllerTest {
@Mock
private LightSensorController mMockLightSensorController;
@Mock
- private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
- @Mock
private DisplayManagerFlags mFlags;
@Mock
private BrightnessModifier mMockModifier;
@@ -93,6 +92,8 @@ public class BrightnessClamperControllerTest {
@Mock
private TestDisplayListenerModifier mMockDisplayListenerModifier;
@Mock
+ private TestDeviceConfigListenerModifier mMockDeviceConfigListenerModifier;
+ @Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
@Mock
@@ -104,8 +105,9 @@ public class BrightnessClamperControllerTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestInjector = new TestInjector(List.of(mMockClamper),
- List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier));
+ mTestInjector = new TestInjector(
+ List.of(mMockModifier, mMockStatefulModifier,
+ mMockDisplayListenerModifier, mMockDeviceConfigListenerModifier));
when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
@@ -125,16 +127,11 @@ public class BrightnessClamperControllerTest {
@Test
public void testConstructor_doesNotStartsLightSensorController() {
- verify(mMockLightSensorController, never()).restart();
- }
-
- @Test
- public void testConstructor_startsLightSensorController() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
mClamperController = createBrightnessClamperController();
- verify(mMockLightSensorController).restart();
+ verify(mMockLightSensorController, never()).restart();
}
@Test
@@ -150,7 +147,7 @@ public class BrightnessClamperControllerTest {
}
@Test
- public void testDelegatesPropertiesChangeToClamper() {
+ public void testDelegatesPropertiesChangeToDeviceConfigLisener() {
ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> captor = ArgumentCaptor.forClass(
DeviceConfig.OnPropertiesChangedListener.class);
verify(mMockDeviceConfigParameterProvider)
@@ -158,14 +155,7 @@ public class BrightnessClamperControllerTest {
captor.getValue().onPropertiesChanged(mMockProperties);
- verify(mMockClamper).onDeviceConfigChanged();
- }
-
- @Test
- public void testOnDisplayChanged_DelegatesToClamper() {
- mClamperController.onDisplayChanged(mMockDisplayDeviceData);
-
- verify(mMockClamper).onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockDeviceConfigListenerModifier).onDeviceConfigChanged();
}
@Test
@@ -177,20 +167,43 @@ public class BrightnessClamperControllerTest {
@Test
public void testOnDisplayChanged_doesNotRestartLightSensor() {
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
}
@Test
public void testOnDisplayChanged_restartsLightSensor() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockLightSensorController, never()).stop();
verify(mMockLightSensorController).restart();
}
@Test
+ public void testOnDisplayChanged_doesNotRestartLightSensor_screenOff() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_OFF);
+ reset(mMockLightSensorController);
+
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
+ }
+
+ @Test
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
@@ -225,105 +238,6 @@ public class BrightnessClamperControllerTest {
}
@Test
- public void testClamp_inactiveClamperNotApplied() {
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(false);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
-
- DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
- mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
- assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(0,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertEquals(initialSlowChange, state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperApplied_brightnessAboveMax() {
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
-
- DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
- mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
- assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertFalse(state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperApplied_brightnessBelowMax() {
- float initialBrightness = 0.6f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.8f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
-
- DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
- mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
- assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertFalse(state.isSlowChange());
- }
-
- @Test
- public void testClamp_activeClamperAppliedTwoTimes_keepsSlowChange() {
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
- float clampedBrightness = 0.6f;
- float customAnimationRate = 0.01f;
- when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
- when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
- when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
- when(mMockClamper.isActive()).thenReturn(true);
- mTestInjector.mCapturedChangeListener.onChanged();
- mTestHandler.flush();
- // first call of clamp method
- mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
- // immediately second call of clamp method
- DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
- mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
- assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
- assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
- assertEquals(BrightnessReason.MODIFIER_THROTTLED,
- state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
- assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
- assertEquals(initialSlowChange, state.isSlowChange());
- }
-
- @Test
public void testClamp_activeClamperApplied_confirmBrightnessOverrideStateReturned() {
float initialBrightness = 0.8f;
boolean initialSlowChange = false;
@@ -352,7 +266,6 @@ public class BrightnessClamperControllerTest {
mClamperController.stop();
verify(mMockLightSensorController).stop();
verify(mMockModifier).stop();
- verify(mMockClamper).stop();
}
@Test
@@ -377,6 +290,24 @@ public class BrightnessClamperControllerTest {
verify(mMockExternalListener).onChanged();
}
+ @Test
+ public void test_doesNotScheduleRecalculateBeforeStart() {
+ mTestInjector = new TestInjector(List.of()) {
+ @Override
+ List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
+ Handler handler, BrightnessClamperController.ClamperChangeListener listener,
+ BrightnessClamperController.DisplayDeviceData displayDeviceData,
+ float currentBrightness) {
+ listener.onChanged();
+ return super.getModifiers(flags, context, handler, listener, displayDeviceData,
+ currentBrightness);
+ }
+ };
+ mClamperController = createBrightnessClamperController();
+
+ assertThat(mTestHandler.getPendingMessages()).isEmpty();
+ }
+
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager, 0);
@@ -390,20 +321,18 @@ public class BrightnessClamperControllerTest {
BrightnessClamperController.StatefulModifier {
}
+ interface TestDeviceConfigListenerModifier extends BrightnessStateModifier,
+ BrightnessClamperController.DeviceConfigListener {
+
+ }
+
private class TestInjector extends BrightnessClamperController.Injector {
- private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
- mClampers;
private final List<BrightnessStateModifier> mModifiers;
-
private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener;
private LightSensorController.LightSensorListener mCapturedLightSensorListener;
- private TestInjector(
- List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
- clampers,
- List<BrightnessStateModifier> modifiers) {
- mClampers = clampers;
+ private TestInjector(List<BrightnessStateModifier> modifiers) {
mModifiers = modifiers;
}
@@ -413,19 +342,11 @@ public class BrightnessClamperControllerTest {
}
@Override
- List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
- Handler handler,
- BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data,
- DisplayManagerFlags flags, Context context, float currentBrightness) {
- mCapturedChangeListener = clamperChangeListener;
- return mClampers;
- }
-
- @Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, BrightnessClamperController.ClamperChangeListener listener,
- BrightnessClamperController.DisplayDeviceData displayDeviceData) {
+ BrightnessClamperController.DisplayDeviceData displayDeviceData,
+ float currentBrightness) {
+ mCapturedChangeListener = listener;
return mModifiers;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
deleted file mode 100644
index c4898da62d81..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
+++ /dev/null
@@ -1,239 +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.server.display.brightness.clamper;
-
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.Temperature;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
-import com.android.server.testutils.FakeDeviceConfigInterface;
-import com.android.server.testutils.TestHandler;
-
-import junitparams.JUnitParamsRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(JUnitParamsRunner.class)
-public class BrightnessPowerClamperTest {
- private static final String TAG = "BrightnessPowerClamperTest";
- private static final float FLOAT_TOLERANCE = 0.001f;
-
- private static final String DISPLAY_ID = "displayId";
- @Mock
- private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
- private TestPmicMonitor mPmicMonitor;
- private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
- new FakeDeviceConfigInterface();
- private final TestHandler mTestHandler = new TestHandler(null);
- private final TestInjector mTestInjector = new TestInjector();
- private BrightnessPowerClamper mClamper;
- private final float mCurrentBrightness = 0.6f;
- private PowerChangeListener mPowerChangeListener;
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mClamper = new BrightnessPowerClamper(mTestInjector, mTestHandler,
- mMockClamperChangeListener, new TestPowerData(), mCurrentBrightness);
- mPowerChangeListener = mClamper.getPowerChangeListener();
- mPmicMonitor = mTestInjector.getPmicMonitor(mPowerChangeListener, null, 5, 10);
- mPmicMonitor.setPowerChangeListener(mPowerChangeListener);
- mTestHandler.flush();
- }
-
- @Test
- public void testTypeIsPower() {
- assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
- }
-
- @Test
- public void testNoThrottlingData() {
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
- mTestHandler.flush();
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
-
- mTestHandler.flush();
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
- mTestHandler.flush();
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
- mTestHandler.flush();
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
- mTestHandler.flush();
- assertFalse(mClamper.isActive());
- assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
- // update a new device config for power-throttling.
- mClamper.onDisplayChanged(new TestPowerData(
- List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
- mPmicMonitor.setAvgPowerConsumed(200f);
- float expectedBrightness = 0.5f;
- expectedBrightness = expectedBrightness * mCurrentBrightness;
- mTestHandler.flush();
-
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
-
- mPmicMonitor.setAvgPowerConsumed(100f);
- // No cap applied for Temperature.THROTTLING_NONE
- expectedBrightness = PowerManager.BRIGHTNESS_MAX;
- mTestHandler.flush();
-
- // clamper should not be active anymore.
- assertFalse(mClamper.isActive());
- // Assume current brightness as max, as there is no throttling.
- assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
- }
-
-
- private static class TestPmicMonitor extends PmicMonitor {
- private Temperature mCurrentTemperature;
- private PowerChangeListener mListener;
- TestPmicMonitor(PowerChangeListener listener,
- IThermalService thermalService,
- int pollingTimeMax, int pollingTimeMin) {
- super(listener, thermalService, pollingTimeMax, pollingTimeMin);
- }
- public void setAvgPowerConsumed(float power) {
- int status = mCurrentTemperature.getStatus();
- mListener.onChanged(power, status);
- }
- public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
- mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
- }
- public void setPowerChangeListener(PowerChangeListener listener) {
- mListener = listener;
- }
- }
-
- private class TestInjector extends BrightnessPowerClamper.Injector {
- @Override
- TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
- IThermalService thermalService,
- int minPollingTimeMillis, int maxPollingTimeMillis) {
- mPmicMonitor = new TestPmicMonitor(listener, thermalService, maxPollingTimeMillis,
- minPollingTimeMillis);
- return mPmicMonitor;
- }
-
- @Override
- DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
- return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
- }
- }
-
- private static class TestPowerData implements BrightnessPowerClamper.PowerData {
-
- private final String mUniqueDisplayId;
- private final String mDataId;
- private final PowerThrottlingData mData;
- private final PowerThrottlingConfigData mConfigData;
-
- private TestPowerData() {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
- }
-
- private TestPowerData(List<ThrottlingLevel> data) {
- this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
- }
-
- private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
- mUniqueDisplayId = uniqueDisplayId;
- mDataId = dataId;
- mData = PowerThrottlingData.create(data);
- mConfigData = new PowerThrottlingConfigData(0.1f, 10, 20, 10);
- }
-
- @NonNull
- @Override
- public String getUniqueDisplayId() {
- return mUniqueDisplayId;
- }
-
- @NonNull
- @Override
- public String getPowerThrottlingDataId() {
- return mDataId;
- }
-
- @Nullable
- @Override
- public PowerThrottlingData getPowerThrottlingData() {
- return mData;
- }
-
- @Nullable
- @Override
- public PowerThrottlingConfigData getPowerThrottlingConfigData() {
- return mConfigData;
- }
- }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
new file mode 100644
index 000000000000..b438d745806c
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static android.os.PowerManager.BRIGHTNESS_MAX;
+
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerModifierTest {
+ private static final String DISPLAY_ID = "displayId";
+ private static final int NO_MODIFIER = 0;
+ private static final float CUSTOM_ANIMATION_RATE = 10f;
+ private static final PowerThrottlingConfigData DEFAULT_CONFIG = new PowerThrottlingConfigData(
+ 0.1f, CUSTOM_ANIMATION_RATE, 20, 10);
+ private static final float DEFAULT_BRIGHTNESS = 0.6f;
+
+ @Mock
+ private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+ @Mock
+ private DisplayDeviceConfig mMockDisplayDeviceConfig;
+ @Mock
+ private IBinder mMockBinder;
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private final TestInjector mTestInjector = new TestInjector();
+ private BrightnessPowerModifier mModifier;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDisplayDeviceConfig.getPowerThrottlingConfigData()).thenReturn(DEFAULT_CONFIG);
+ mModifier = new BrightnessPowerModifier(mTestInjector, mTestHandler,
+ mMockClamperChangeListener, ClamperTestUtilsKt.createDisplayDeviceData(
+ mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+ DisplayDeviceConfig.DEFAULT_ID), DEFAULT_BRIGHTNESS);
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+ }
+
+ @Test
+ public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ float powerQuota = 100f;
+ float avgPowerConsumed = 200f;
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, powerQuota)));
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+
+ float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+ mTestHandler.flush();
+
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+ }
+
+ @Test
+ public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ float powerQuota = 100f;
+ float avgPowerConsumed = 200f;
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, powerQuota)));
+
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+ float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+ }
+
+ @Test
+ public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ // no config yet, modifier inactive
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+ // update a new device config for power-throttling.
+ onDisplayChanged(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)));
+ mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(200f);
+
+ mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+ // No cap applied for Temperature.THROTTLING_NONE
+ mTestHandler.flush();
+
+ // Modifier should not be active anymore, no throttling
+ assertModifierState(DEFAULT_BRIGHTNESS,
+ BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+ }
+
+ private void onDisplayChanged(List<ThrottlingLevel> throttlingLevels) {
+ Map<String, PowerThrottlingData> throttlingLevelsMap = new HashMap<>();
+ throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID,
+ PowerThrottlingData.create(throttlingLevels));
+ when(mMockDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId())
+ .thenReturn(throttlingLevelsMap);
+ mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData(
+ mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+ DisplayDeviceConfig.DEFAULT_ID));
+ }
+
+ private void assertModifierState(
+ float currentBrightness,
+ float maxBrightness, float brightness, float customAnimationRate,
+ boolean isActive) {
+ ModifiersAggregatedState modifierState = new ModifiersAggregatedState();
+ DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder();
+ stateBuilder.setBrightness(currentBrightness);
+
+ int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC
+ : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER;
+
+ mModifier.applyStateChange(modifierState);
+ assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness);
+ assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason);
+
+ mModifier.apply(mMockRequest, stateBuilder);
+
+ assertThat(stateBuilder.getMaxBrightness())
+ .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness);
+ assertThat(stateBuilder.getBrightness())
+ .isWithin(BrightnessSynchronizer.EPSILON).of(brightness);
+ assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason);
+ assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier);
+ assertThat(stateBuilder.getCustomAnimationRate()).isEqualTo(customAnimationRate);
+ }
+
+ private static class TestPmicMonitor extends PmicMonitor {
+ private Temperature mCurrentTemperature;
+ private float mCurrentAvgPower;
+
+ private final PowerChangeListener mListener;
+ TestPmicMonitor(PowerChangeListener listener,
+ IThermalService thermalService,
+ int pollingTimeMax, int pollingTimeMin) {
+ super(listener, thermalService, pollingTimeMax, pollingTimeMin);
+ mListener = listener;
+ }
+ public void setAvgPowerConsumed(float power) {
+ mCurrentAvgPower = power;
+ mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+ mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+ mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ }
+
+ private class TestInjector extends BrightnessPowerModifier.Injector {
+ private TestPmicMonitor mCapturedPmicMonitor;
+ @NonNull
+ @Override
+ TestPmicMonitor getPmicMonitor(PowerChangeListener listener, IThermalService thermalService,
+ int minPollingTimeMillis, int maxPollingTimeMillis) {
+ mCapturedPmicMonitor = new TestPmicMonitor(listener, thermalService,
+ maxPollingTimeMillis, minPollingTimeMillis);
+ return mCapturedPmicMonitor;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
index be4e7c7a9edd..7e4042ed2d05 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
@@ -44,6 +44,8 @@ public class DisplayDimModifierTest {
private static final float MIN_DIM_AMOUNT = 0.05f;
private static final float DIM_CONFIG = 0.4f;
+ private static final int DISPLAY_ID = 3;
+
@Mock
private Context mMockContext;
@@ -66,9 +68,9 @@ public class DisplayDimModifierTest {
R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
when(mMockPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+ DISPLAY_ID, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
- mModifier = new DisplayDimModifier(mMockContext);
+ mModifier = new DisplayDimModifier(DISPLAY_ID, mMockContext);
mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index ace6aae1a8fc..6defadf44d05 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -47,8 +47,10 @@ import static com.android.server.am.Flags.FLAG_AVOID_RESOLVING_TYPE;
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
+
import static com.google.common.truth.Truth.assertThat;
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.assertNotEquals;
@@ -87,6 +89,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
import android.app.backup.BackupAnnotations;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -98,6 +101,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -105,6 +109,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IProgressListener;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -201,6 +206,16 @@ public class ActivityManagerServiceTest {
private static final String TEST_AUTHORITY = "test_authority";
private static final String TEST_MIME_TYPE = "application/test_type";
+ private static final Uri TEST_URI = Uri.parse("content://com.example/people");
+ private static final int TEST_CREATOR_UID = 12345;
+ private static final String TEST_CREATOR_PACKAGE = "android.content.testCreatorPackage";
+ private static final String TEST_TYPE = "testType";
+ private static final String TEST_IDENTIFIER = "testIdentifier";
+ private static final String TEST_CATEGORY = "testCategory";
+ private static final String TEST_LAUNCH_TOKEN = "testLaunchToken";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ "TestClass");
+ private static final int ALL_SET_FLAG = 0xFFFFFFFF;
private static final int[] UID_RECORD_CHANGES = {
UidRecord.CHANGE_PROCSTATE,
@@ -959,28 +974,37 @@ public class ActivityManagerServiceTest {
@Test
@SuppressWarnings("GuardedBy")
public void testBroadcastStickyIntent_verifyTypeNotResolved() throws Exception {
- final Intent intent = new Intent(TEST_ACTION1);
- final Uri uri = new Uri.Builder()
- .scheme(SCHEME_CONTENT)
- .authority(TEST_AUTHORITY)
- .path("green")
- .build();
- intent.setData(uri);
- broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
- assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
- StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
- TEST_MIME_TYPE));
- when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+ MockitoSession mockitoSession =
+ ExtendedMockito.mockitoSession().mockStatic(IpcDataCache.class).startMocking();
- addUidRecord(TEST_UID, TEST_PACKAGE);
- final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
- final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
- intentFilter.addDataType(TEST_MIME_TYPE);
- final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
- TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
- Context.RECEIVER_EXPORTED);
- assertNotNull(resultIntent);
- verify(mContentResolver, never()).getType(any());
+ try {
+ final Intent intent = new Intent(TEST_ACTION1);
+ final Uri uri = new Uri.Builder()
+ .scheme(SCHEME_CONTENT)
+ .authority(TEST_AUTHORITY)
+ .path("green")
+ .build();
+ intent.setData(uri);
+ broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
+ assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
+ StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
+ TEST_MIME_TYPE));
+ when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+ ExtendedMockito.doNothing().when(
+ () -> IpcDataCache.invalidateCache(anyString(), anyString()));
+
+ addUidRecord(TEST_UID, TEST_PACKAGE);
+ final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+ final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
+ intentFilter.addDataType(TEST_MIME_TYPE);
+ final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
+ TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
+ Context.RECEIVER_EXPORTED);
+ assertNotNull(resultIntent);
+ verify(mContentResolver, never()).getType(any());
+ } finally {
+ mockitoSession.finishMocking();
+ }
}
@SuppressWarnings("GuardedBy")
@@ -1402,6 +1426,34 @@ public class ActivityManagerServiceTest {
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
}
+ @Test
+ public void testUseCloneForCreatorTokenAndOriginalIntent_createSameIntentCreatorToken() {
+ Intent testIntent = new Intent(TEST_ACTION1)
+ .setComponent(TEST_COMPONENT)
+ .setDataAndType(TEST_URI, TEST_TYPE)
+ .setIdentifier(TEST_IDENTIFIER)
+ .addCategory(TEST_CATEGORY);
+ testIntent.setOriginalIntent(new Intent(TEST_ACTION2));
+ testIntent.setSelector(new Intent(TEST_ACTION3));
+ testIntent.setSourceBounds(new Rect(0, 0, 100, 100));
+ testIntent.setLaunchToken(TEST_LAUNCH_TOKEN);
+ testIntent.addFlags(ALL_SET_FLAG)
+ .addExtendedFlags(ALL_SET_FLAG);
+ ClipData testClipData = ClipData.newHtmlText("label", "text", "<html/>");
+ testClipData.addItem(new ClipData.Item(new Intent(TEST_ACTION1)));
+ testClipData.addItem(new ClipData.Item(TEST_URI));
+ testIntent.putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1);
+
+ ActivityManagerService.IntentCreatorToken tokenForFullIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent);
+ ActivityManagerService.IntentCreatorToken tokenForCloneIntent =
+ new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+ TEST_CREATOR_PACKAGE, testIntent.cloneForCreatorToken());
+
+ assertThat(tokenForFullIntent.getKeyFields()).isEqualTo(tokenForCloneIntent.getKeyFields());
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
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 2461e9e79acf..8aaa72339c5b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -119,6 +119,8 @@ public class BackupAgentConnectionManagerTest {
mTestApplicationInfo = new ApplicationInfo();
mTestApplicationInfo.packageName = TEST_PACKAGE;
+ mTestApplicationInfo.processName = TEST_PACKAGE;
+ mTestApplicationInfo.uid = Process.FIRST_APPLICATION_UID + 1;
mBackupAgentResult = null;
mTestThread = null;
@@ -134,8 +136,8 @@ public class BackupAgentConnectionManagerTest {
@Test
public void bindToAgentSynchronous_amReturnsFailure_returnsNullAndClearsPendingBackups()
throws Exception {
- when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
- anyInt(), anyBoolean())).thenReturn(false);
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(false);
IBackupAgent result = mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
@@ -147,8 +149,8 @@ public class BackupAgentConnectionManagerTest {
@Test
public void bindToAgentSynchronous_agentDisconnectedCalled_returnsNullAndClearsPendingBackups()
throws Exception {
- when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
- anyInt(), anyBoolean())).thenReturn(true);
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
// This is so that IBackupAgent.Stub.asInterface() works.
when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
@@ -172,24 +174,7 @@ public class BackupAgentConnectionManagerTest {
@Test
public void bindToAgentSynchronous_agentConnectedCalled_returnsBackupAgent() throws Exception {
- when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
- anyInt(), anyBoolean())).thenReturn(true);
- // This is so that IBackupAgent.Stub.asInterface() works.
- when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
- when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
-
- // This is going to block until it receives the callback so we need to run it on a
- // separate thread.
- Thread testThread = new Thread(() -> setBackupAgentResult(
- mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
- "backup-agent-connection-manager-test");
- testThread.start();
- // Give the testThread a head start, otherwise agentConnected() might run before
- // bindToAgentSynchronous() is called.
- Thread.sleep(500);
- mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
- testThread.join();
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub);
verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt());
@@ -198,8 +183,8 @@ public class BackupAgentConnectionManagerTest {
@Test
public void bindToAgentSynchronous_unexpectedAgentConnected_doesNotReturnWrongAgent()
throws Exception {
- when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
- anyInt(), anyBoolean())).thenReturn(true);
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
// This is so that IBackupAgent.Stub.asInterface() works.
when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
@@ -390,11 +375,75 @@ public class BackupAgentConnectionManagerTest {
@Test
public void unbindAgent_callsAmUnbindBackupAgent() throws Exception {
- mConnectionManager.unbindAgent(mTestApplicationInfo);
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
verify(mActivityManager).unbindBackupAgent(eq(mTestApplicationInfo));
}
+ @Test
+ public void unbindAgent_doNotAllowKill_doesNotKillApp() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isCoreApp_doesNotKillApp() throws Exception {
+ mTestApplicationInfo.uid = 1000;
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_notCurrentConnection_killsApp() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_inRestrictedMode_killsApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_notInRestrictedMode_doesNotKillApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isRestore_noKillAfterRestore_doesNotKillApp()
+ throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+ mTestApplicationInfo.flags = 0;
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+ }
+
+ @Test
+ public void unbindAgent_allowKill_isRestore_killAfterRestore_killsApp() throws Exception {
+ bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+ mTestApplicationInfo.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
+
+ mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+ verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+ }
+
// Needed because variables can't be assigned directly inside lambdas in Java.
private void setBackupAgentResult(IBackupAgent result) {
mBackupAgentResult = result;
@@ -404,4 +453,23 @@ public class BackupAgentConnectionManagerTest {
private void setTestThread(Thread thread) {
mTestThread = thread;
}
+
+ private void bindAndConnectToTestAppAgent(int backupMode) throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ anyBoolean())).thenReturn(true);
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, backupMode,
+ BackupDestination.CLOUD)), "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
+ testThread.join();
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index a1937cec706c..9b8a7cca7358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -23,6 +23,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -33,20 +35,24 @@ import android.content.res.Resources;
import android.location.ILocationListener;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
+import android.location.flags.Flags;
import android.location.provider.ProviderRequest;
import android.os.IBinder;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.google.common.util.concurrent.MoreExecutors;
@@ -54,6 +60,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,8 +82,12 @@ public class LocationManagerServiceTest {
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Spy private FakeAbstractLocationProvider mProviderWithPermission;
@Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+ @Mock private ProxyPopulationDensityProvider mPopulationDensityProvider;
@Mock private ILocationListener mLocationListener;
@Mock private IBinder mBinder;
@Mock private Context mContext;
@@ -172,6 +183,32 @@ public class LocationManagerServiceTest {
}
@Test
+ public void testSetLocationFudgerCache_withFeatureFlagDisabled_isNotCalled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager, never()).setLocationFudgerCache(any());
+ }
+
+ @Test
+ public void testSetLocationFudgerCache_withFeatureFlagEnabled_isCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager).setLocationFudgerCache(cache);
+ }
+
+ @Test
public void testHasProvider_noPermission() {
assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
new file mode 100644
index 000000000000..6b7eda26b945
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.location.fudger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+public class LocationFudgerCacheTest {
+
+ private static final String TAG = "LocationFudgerCacheTest";
+
+ private static final long TIMES_SQUARE_S2_ID =
+ S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+ private static final double[] POINT_IN_TIMES_SQUARE = {40.75889599346095, -73.9851300385147};
+
+ private static final double[] POINT_OUTSIDE_TIMES_SQUARE = {48.858093, 2.294694};
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
+ public void hasDefaultValue_isInitiallyFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_uponQueryError_isStillFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_afterSuccessfulQuery_isTrue()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ assertThat(cache.hasDefaultValue()).isTrue();
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedOutsideOfCache_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ int defaultLevel = 2;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(defaultLevel);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_OUTSIDE_TIMES_SQUARE[0],
+ POINT_OUTSIDE_TIMES_SQUARE[1])).isEqualTo(defaultLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedValueIsCached_returnsCachedValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]))
+ .isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenStarting_queriesDefaultValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifDidntGetDefaultValue_queriesItAgain() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ verify(provider, times(2)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifReceivedDefaultValue_doesNotQueriesIt()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ // Verify getDefaultCoarseningLevel did not get called again
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenSuccessfullyQueriesDefaultValue_storesResult()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(level);
+
+ // Query any uncached location
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryingDefaultValueFails_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ // Query any uncached location. The default value is 0
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(0);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsNotCached_queriesProvider() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenProviderIsQueried_resultIsCached() throws RemoteException {
+ double lat = POINT_IN_TIMES_SQUARE[0];
+ double lng = POINT_IN_TIMES_SQUARE[1];
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ int level = cache.getCoarseningLevel(lat, lng);
+ assertThat(level).isEqualTo(0); // default value
+
+ ArgumentCaptor<IS2CellIdsCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2CellIdsCallback.class);
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), argumentCaptor.capture());
+
+ // Results from the proxy should set the cache
+ int expectedLevel = 4;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(lat, lng);
+ Long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+ IS2CellIdsCallback cb = argumentCaptor.getValue();
+ long[] answer = new long[] {s2CellId};
+ cb.onResult(answer);
+
+ int level2 = cache.getCoarseningLevel(lat, lng);
+ assertThat(level2).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_doesNotRefreshIt() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.addToCache(TIMES_SQUARE_S2_ID);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_askForMaxCacheSizeElems() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int numAdditionalCells = cache.MAX_CACHE_SIZE - 1;
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), eq(numAdditionalCells), any());
+ }
+
+
+ @Test
+ public void locationFudgerCache_canContainUpToMaxSizeItems() {
+ // This test has two sequences of arrange-act-assert.
+ // The first checks that the cache correctly store up to MAX_CACHE_SIZE items.
+ // The second checks that any new element replaces the oldest in the cache.
+
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int size = cache.MAX_CACHE_SIZE;
+
+ double[][] latlngs = new double[size][2];
+ long[] cells = new long[size];
+ int[] expectedLevels = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ // Create arbitrary lat/lngs.
+ latlngs[i][0] = 10.0 * i;
+ latlngs[i][1] = 10.0 * i;
+
+ expectedLevels[i] = 10; // we set some arbitrary S2 level for each latlng.
+
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latlngs[i][0], latlngs[i][1]);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevels[i]);
+ cells[i] = s2CellId;
+ }
+
+ // Act.
+ cache.addToCache(cells);
+
+ // Assert: check that the cache contains these latlngs and returns the correct level.
+ for (int i = 0; i < size; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+
+ // Second assertion: A new value evicts the oldest one.
+
+ // Arrange.
+ int expectedLevel = 25;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(-10.0, -180.0);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+
+ // Act.
+ cache.addToCache(s2CellId);
+
+ // Assert: the new point is in the cache.
+ assertThat(cache.getCoarseningLevel(-10.0, -180.0)).isEqualTo(expectedLevel);
+ // Assert: all but the oldest point are still in cache.
+ for (int i = 0; i < size - 1; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+ // Assert: the oldest point has been evicted.
+ assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1]))
+ .isEqualTo(0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 4e9b6c78edfd..835705d49e6e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -22,14 +22,23 @@ import static com.android.server.location.LocationUtils.createLocation;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.location.Location;
+import android.location.flags.Flags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +64,9 @@ public class LocationFudgerTest {
private LocationFudger mFudger;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
long seed = System.currentTimeMillis();
@@ -162,4 +174,64 @@ public class LocationFudgerTest {
input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
0);
}
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsDisabled_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNoDefaultValue_cacheIsNotUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(false).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(true).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ Location fine = createLocation("test", mRandom);
+ mFudger.createCoarse(fine);
+
+ // We can't verify that the coordinatese of "fine" are passed to the API due to the addition
+ // of the offset. We must use anyDouble().
+ verify(cache).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_newAlgorithm_snapsToCenterOfS2Cell_testVector() {
+ // NB: a complete test vector is in
+ // frameworks/base/services/tests/mockingservicestests/src/com/android/server/...
+ // location/geometry/S2CellIdUtilsTest.java
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ // Arbitrary location in Times Square, NYC
+ double[] latLng = new double[] {40.758896, -73.985130};
+ int s2Level = 1;
+ // The level-2 S2 cell around this location is "8c", its center is:
+ double[] expected = { 21.037511025421814, -67.38013505195958 };
+
+ double[] center = mFudger.snapToCenterOfS2Cell(latLng[0], latLng[1], s2Level);
+
+ assertThat(center[0]).isEqualTo(expected[0]);
+ assertThat(center[1]).isEqualTo(expected[1]);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 09282646ff68..6d78defe2943 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -40,6 +40,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +73,7 @@ import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
+import android.location.provider.IS2LevelCallback;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -98,8 +100,10 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import org.junit.After;
import org.junit.Before;
@@ -1432,6 +1436,72 @@ public class LocationProviderManagerTest {
PERMISSION_FINE)).isEqualTo(location);
}
+ @Test
+ public void testLocationFudger_withFlagDisabled_cacheIsNotSetAndOldAlgoIsUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabledButNoDefaults_oldAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+
+ // Act: the provider didn't provide a default
+ cb.onError();
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabled_cacheIsSetAndNewAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int defaultLevel = 2;
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+ cb.onResult(defaultLevel);
+
+ Location test = new Location("any-provider");
+ test.setLatitude(10.0);
+ test.setLongitude(20.0);
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ // We can't test that 10.0, 20.0 was passed due to the offset. We only test that a call
+ // happened.
+ verify(provider).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 405024cc0e34..769f071e3ddc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -134,7 +134,7 @@ public class ApexManagerTest {
mMockSystem.system().validateFinalState();
mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class),
mock(RemovePackageHelper.class), mock(DeletePackageHelper.class),
- mock(BroadcastHelper.class), mock(InstallDependencyHelper.class));
+ mock(BroadcastHelper.class));
}
@NonNull
diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp
index a31a3fb65700..f235da2fea7f 100644
--- a/services/tests/ondeviceintelligencetests/Android.bp
+++ b/services/tests/ondeviceintelligencetests/Android.bp
@@ -44,14 +44,18 @@ android_test {
"truth",
"frameworks-base-testutils",
"androidx.test.rules",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_ondevice_intelligence_module"), {
+ "true": [
+ "service-ondeviceintelligence.impl",
+ ],
+ default: [],
+ }),
libs: [
"android.test.mock.stubs.system",
"android.test.base.stubs.system",
"android.test.runner.stubs.system",
],
-
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
diff --git a/services/tests/ondeviceintelligencetests/OWNERS b/services/tests/ondeviceintelligencetests/OWNERS
index 09774f78d712..a4fc7582a785 100644
--- a/services/tests/ondeviceintelligencetests/OWNERS
+++ b/services/tests/ondeviceintelligencetests/OWNERS
@@ -1 +1,3 @@
-file:/core/java/android/app/ondeviceintelligence/OWNERS
+shiqing@google.com
+sandeepbandaru@google.com
+shivanker@google.com
diff --git a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
index d12579cd6b11..28ccb84c38f3 100644
--- a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
+++ b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.ondeviceintelligence.InferenceInfo;
import android.os.Bundle;
import android.os.PersistableBundle;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.util.Base64;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +37,8 @@ import java.util.List;
public class InferenceInfoStoreTest {
InferenceInfoStore inferenceInfoStore;
+ public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
@Before
public void setUp() {
inferenceInfoStore = new InferenceInfoStore(1000);
@@ -46,7 +47,7 @@ public class InferenceInfoStoreTest {
@Test
public void testInferenceInfoParsesFromBundleSuccessfully() throws Exception {
Bundle bundle = new Bundle();
- bundle.putByteArray(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putByteArray(INFERENCE_INFO_BUNDLE_KEY,
getInferenceInfoBytes(1, 1, 100));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
@@ -59,7 +60,7 @@ public class InferenceInfoStoreTest {
@Test
public void testInferenceInfoParsesFromPersistableBundleSuccessfully() throws Exception {
PersistableBundle bundle = new PersistableBundle();
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, 1, 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
@@ -74,11 +75,11 @@ public class InferenceInfoStoreTest {
public void testEvictionAfterMaxAge() throws Exception {
PersistableBundle bundle = new PersistableBundle();
long testStartTime = System.currentTimeMillis();
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 10,
testStartTime + 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
- bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+ bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 5,
testStartTime + 100), Base64.DEFAULT));
inferenceInfoStore.addInferenceInfoFromBundle(bundle);
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 313c01d5ce58..7248833d876c 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -58,6 +58,7 @@ import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
import android.hardware.power.WorkDuration;
import android.os.Binder;
import android.os.CpuHeadroomParamsInternal;
@@ -159,6 +160,7 @@ public class HintManagerServiceTest {
private HintManagerService mService;
private ChannelConfig mConfig;
+ private SupportInfo mSupportInfo;
private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
return new Answer<Long>() {
@@ -179,6 +181,12 @@ public class HintManagerServiceTest {
mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>();
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ mSupportInfo = new SupportInfo();
+ mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+ mSupportInfo.headroom.isCpuSupported = true;
+ mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
+ mSupportInfo.headroom.isGpuSupported = true;
+ mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
@@ -205,6 +213,7 @@ public class HintManagerServiceTest {
SESSION_IDS[2]));
when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
+ when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo);
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -1247,28 +1256,22 @@ public class HintManagerServiceTest {
@Test
public void testCpuHeadroomCache() throws Exception {
- when(mIPowerMock.getCpuHeadroomMinIntervalMillis()).thenReturn(2000L);
CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
CpuHeadroomParams halParams1 = new CpuHeadroomParams();
halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
- halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
halParams1.tids = new int[]{Process.myPid()};
CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
params2.usesDeviceHeadroom = true;
params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
- params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
CpuHeadroomParams halParams2 = new CpuHeadroomParams();
halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
- halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
halParams2.tids = new int[]{};
CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
- params3.selectionType = CpuHeadroomParams.SelectionType.ALL;
CpuHeadroomParams halParams3 = new CpuHeadroomParams();
halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
- halParams3.selectionType = CpuHeadroomParams.SelectionType.ALL;
halParams3.tids = new int[]{Process.myPid()};
// this params should not be cached as the window is not default
@@ -1276,15 +1279,14 @@ public class HintManagerServiceTest {
params4.calculationWindowMillis = 123;
CpuHeadroomParams halParams4 = new CpuHeadroomParams();
halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
- halParams4.selectionType = CpuHeadroomParams.SelectionType.ALL;
halParams4.calculationWindowMillis = 123;
halParams4.tids = new int[]{Process.myPid()};
float headroom1 = 0.1f;
CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
- float[] headroom2 = new float[] {0.2f, 0.2f};
- CpuHeadroomResult halRet2 = CpuHeadroomResult.perCoreHeadroom(headroom2);
+ float headroom2 = 0.2f;
+ CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2);
when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
float headroom3 = 0.3f;
CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
@@ -1296,8 +1298,6 @@ public class HintManagerServiceTest {
HintManagerService service = createService();
clearInvocations(mIPowerMock);
- service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
- verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1348,7 +1348,6 @@ public class HintManagerServiceTest {
@Test
public void testGpuHeadroomCache() throws Exception {
- when(mIPowerMock.getGpuHeadroomMinIntervalMillis()).thenReturn(2000L);
GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
GpuHeadroomParams halParams1 = new GpuHeadroomParams();
halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
@@ -1369,8 +1368,6 @@ public class HintManagerServiceTest {
HintManagerService service = createService();
clearInvocations(mIPowerMock);
- service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
- verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
verify(mIPowerMock, times(2)).getGpuHeadroom(any());
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b48c2d7f5007..376091e4a241 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1257,6 +1257,36 @@ public class PowerManagerServiceTest {
.isEqualTo(WAKEFULNESS_DOZING);
}
+ @EnableFlags({
+ android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER,
+ android.companion.virtualdevice.flags.Flags.FLAG_DISPLAY_POWER_MANAGER_APIS})
+ @Test
+ public void getBrightnessConstraint_valuesMatchDisplayInfo() {
+ final int displayId = 7;
+ final DisplayInfo info = new DisplayInfo();
+ info.brightnessMinimum = 0.12f;
+ info.brightnessDim = 0.34f;
+ info.brightnessDefault = 0.56f;
+ info.brightnessMaximum = 0.78f;
+ when(mDisplayManagerInternalMock.getDisplayInfo(displayId)).thenReturn(info);
+
+ createService();
+ startSystem();
+
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM))
+ .isEqualTo(info.brightnessMinimum);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM))
+ .isEqualTo(info.brightnessMaximum);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT))
+ .isEqualTo(info.brightnessDefault);
+ assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+ displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM))
+ .isEqualTo(info.brightnessDim);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index d7b60cffa623..2b152315eec4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -39,7 +39,6 @@ import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
import android.os.connectivity.WifiActivityEnergyInfo;
-import android.platform.test.ravenwood.RavenwoodConfig;
import android.power.PowerStatsInternal;
import android.util.IntArray;
import android.util.SparseArray;
@@ -66,9 +65,6 @@ import java.util.concurrent.CompletableFuture;
@SuppressWarnings("GuardedBy")
@android.platform.test.annotations.DisabledOnRavenwood
public class BatteryExternalStatsWorkerTest {
- @RavenwoodConfig.Config
- public final RavenwoodConfig mRavenwood = new RavenwoodConfig.Builder().build();
-
private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
private TestPowerStatsInternal mPowerStatsInternal;
private Handler mHandler;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 709f83ba907d..73dcfe77e67f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -36,6 +36,7 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.ConditionVariable;
+import android.os.Handler;
import android.os.Parcel;
import android.os.Process;
import android.os.UidBatteryConsumer;
@@ -81,8 +82,9 @@ public class BatteryUsageStatsProviderTest {
.setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
private MockClock mMockClock = mStatsRule.getMockClock();
- private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
+ private MonotonicClock mMonotonicClock = mStatsRule.getMonotonicClock();
private Context mContext;
+ private PowerStatsStore mPowerStatsStore;
@Before
public void setup() throws IOException {
@@ -93,6 +95,9 @@ public class BatteryUsageStatsProviderTest {
} else {
mContext = InstrumentationRegistry.getContext();
}
+ mPowerStatsStore = spy(new PowerStatsStore(
+ new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
+ mStatsRule.getHandler()));
}
@Test
@@ -274,10 +279,7 @@ public class BatteryUsageStatsProviderTest {
powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
true);
- BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
- powerAttributor, mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
- mMonotonicClock);
+ BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider(0);
return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
}
@@ -331,30 +333,30 @@ public class BatteryUsageStatsProviderTest {
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0);
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 2_000_000);
assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0);
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
"foo", APP_UID, 3_600_000, 90, 3_000_000);
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
"foo", APP_UID, 3_600_000, 90, 3_001_000);
@@ -441,14 +443,15 @@ public class BatteryUsageStatsProviderTest {
assertThat(item.eventTag.string).startsWith(uid + " ");
assertThat(item.batteryChargeUah).isEqualTo(3_600_000);
assertThat(item.batteryLevel).isEqualTo(90);
- assertThat(item.time).isEqualTo((long) 1_000_000);
+ assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + 1_000_000);
}
assertThat(expectedUid).isEqualTo(200);
}
- private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
- String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) {
+ private void assertHistoryItem(MockBatteryStatsImpl batteryStats, BatteryStats.HistoryItem item,
+ int command, int eventCode, String tag, int uid, int batteryChargeUah, int batteryLevel,
+ long elapsedTimeMs) {
assertThat(item.cmd).isEqualTo(command);
assertThat(item.eventCode).isEqualTo(eventCode);
if (tag == null) {
@@ -460,7 +463,7 @@ public class BatteryUsageStatsProviderTest {
assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
assertThat(item.batteryLevel).isEqualTo(batteryLevel);
- assertThat(item.time).isEqualTo(elapsedTimeMs);
+ assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + elapsedTimeMs);
}
@Test
@@ -566,38 +569,66 @@ public class BatteryUsageStatsProviderTest {
assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS);
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
- .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isWithin(0.0001)
- .of(180.0); // 360 mA * 0.5 hours
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
- .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isEqualTo((10 + 20) * MINUTE_IN_MS);
- final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
- .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
- assertThat(uidBatteryConsumer
- .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isWithin(0.1)
- .of(180.0);
+ assertBatteryConsumer(stats, 180.0, (10 + 20) * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, APP_UID, 180.0, (10 + 20) * MINUTE_IN_MS);
stats.close();
}
@Test
public void accumulateBatteryUsageStats() throws Throwable {
- accumulateBatteryUsageStats(10000000, 1);
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ accumulateBatteryUsageStats(batteryStats, 10000000, 0);
// Accumulate every 200 bytes of battery history
- accumulateBatteryUsageStats(200, 2);
- accumulateBatteryUsageStats(50, 5);
+ accumulateBatteryUsageStats(batteryStats, 200, 2);
+ accumulateBatteryUsageStats(batteryStats, 50, 4);
// Accumulate on every invocation of accumulateBatteryUsageStats
- accumulateBatteryUsageStats(0, 7);
+ accumulateBatteryUsageStats(batteryStats, 0, 7);
}
- private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
- int expectedNumberOfUpdates) throws Throwable {
- BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
+ @Test
+ public void getAccumulatedBatteryUsageStats() throws Throwable {
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ // Only accumulate the first 25 minutes
+ accumulateBatteryUsageStats(batteryStats, 200, 1);
+
+ BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider(200);
+
+ // At this point the last stored accumulated stats are `115 - 30 = 85` minutes old
+ BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
+ new BatteryUsageStatsQuery.Builder()
+ .accumulated()
+ .setMaxStatsAgeMs(90 * MINUTE_IN_MS)
+ .build());
+
+ assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+ assertThat(stats.getStatsEndTimestamp()).isEqualTo(30 * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, 60.0, 10 * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, APP_UID, 60.0, 10 * MINUTE_IN_MS);
+
+ stats.close();
+
+ // Now force the usage stats to catch up to the current time
+ stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
+ new BatteryUsageStatsQuery.Builder()
+ .accumulated()
+ .setMaxStatsAgeMs(5 * MINUTE_IN_MS)
+ .build());
+
+ assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
+ assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS);
+
+ stats.close();
+ }
+
+ private void accumulateBatteryUsageStats(MockBatteryStatsImpl batteryStatsImpl,
+ int accumulatedBatteryUsageStatsSpanSize, int expectedNumberOfUpdates)
+ throws Throwable {
+ Handler handler = mStatsRule.getHandler();
+ MockBatteryStatsImpl batteryStats = spy(batteryStatsImpl);
// Note - these two are in microseconds
when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
@@ -610,82 +641,76 @@ public class BatteryUsageStatsProviderTest {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
- PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
- new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
- mStatsRule.getHandler()));
- powerStatsStore.reset();
+ mPowerStatsStore.reset();
int[] count = new int[1];
doAnswer(inv -> {
count[0]++;
- return null;
- }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
+ return inv.callRealMethod();
+ }).when(mPowerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
- MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
- powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
- () -> 3500);
- for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
- powerComponentId++) {
- powerAttributor.setPowerComponentSupported(powerComponentId, true);
- }
- powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
-
- BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
- powerAttributor, mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), powerStatsStore,
- accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
+ BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider(
+ accumulatedBatteryUsageStatsSpanSize);
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(10 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(20 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(30 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ // Make sure the accumulated stats are computed and saved before generating more history
+ mStatsRule.waitForBackgroundThread();
+
+ setTime(50 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOffLocked(APP_UID,
50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
}
setTime(55 * MINUTE_IN_MS);
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
// This section has not been saved yet, but should be added to the accumulated totals
+ setTime(80 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(110 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOffLocked(APP_UID,
110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
}
setTime(115 * MINUTE_IN_MS);
- // Pick up the remainder of battery history that has not yet been accumulated
- provider.accumulateBatteryUsageStats(batteryStats);
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
mStatsRule.waitForBackgroundThread();
- BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
+ BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
new BatteryUsageStatsQuery.Builder().accumulated().build());
assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
@@ -696,29 +721,55 @@ public class BatteryUsageStatsProviderTest {
assertThat(stats.getBatteryCapacity()).isEqualTo(4000); // from PowerProfile
// Total: 10 + 20 + 30 = 60
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS);
+ stats.close();
+
+ mStatsRule.waitForBackgroundThread();
+
+ assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
+ }
+
+ private BatteryUsageStatsProvider createBatteryUsageStatsProvider(
+ int accumulatedBatteryUsageStatsSpanSize) {
+ MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext,
+ mPowerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(),
+ () -> 3500);
+ for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ powerComponentId++) {
+ powerAttributor.setPowerComponentSupported(powerComponentId, true);
+ }
+ powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true);
+
+ return new BatteryUsageStatsProvider(mContext, powerAttributor,
+ mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), mPowerStatsStore,
+ accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock);
+ }
+
+ private static void assertBatteryConsumer(BatteryUsageStats stats, double expectedPowerMah,
+ long expectedDurationMs) {
+ AggregateBatteryConsumer aggregatedConsumer = stats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ assertThat(aggregatedConsumer
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.0001)
- .of(360.0); // 360 mA * 1.0 hour
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .of(expectedPowerMah);
+ assertThat(aggregatedConsumer
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isEqualTo(60 * MINUTE_IN_MS);
+ .isEqualTo(expectedDurationMs);
+ }
+ private static void assertBatteryConsumer(BatteryUsageStats stats, int uid,
+ double expectedPowerMah, long expectedDurationMs) {
final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
- .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
+ .filter(u -> u.getUid() == uid).findFirst().get();
assertThat(uidBatteryConsumer
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.1)
- .of(360.0);
+ .of(expectedPowerMah);
assertThat(uidBatteryConsumer
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isEqualTo(60 * MINUTE_IN_MS);
-
- assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
-
- stats.close();
+ .isEqualTo(expectedDurationMs);
}
private void setTime(long timeMs) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index a3c7ece386c7..9e7e0b646047 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -41,6 +41,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.power.EnergyConsumerStats;
@@ -65,6 +66,7 @@ public class BatteryUsageStatsRule implements TestRule {
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
private String mTestName;
private boolean mCreateTempDirectory;
private File mHistoryDir;
@@ -118,7 +120,7 @@ public class BatteryUsageStatsRule implements TestRule {
clearDirectory();
}
mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(),
- mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
+ mMockClock, mMonotonicClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
synchronized (mBatteryStats) {
@@ -144,6 +146,10 @@ public class BatteryUsageStatsRule implements TestRule {
return mMockClock;
}
+ public MonotonicClock getMonotonicClock() {
+ return mMonotonicClock;
+ }
+
public Handler getHandler() {
return mHandler;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 005ceee703a8..c7fad76969bf 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -29,8 +29,6 @@ import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
@@ -59,13 +57,8 @@ import java.util.regex.Pattern;
@LargeTest
@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
public class CpuPowerStatsCollectorValidationTest {
- @Rule(order = 0)
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule(order = 1)
- public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int WORK_DURATION_MS = 2000;
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index b374a3202fa2..9a38209a7d17 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -77,9 +77,15 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
new PowerStatsUidResolver());
}
- MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
- Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
- super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
+ MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock,
+ File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
+ this(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
+ powerStatsUidResolver);
+ }
+
+ MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, MonotonicClock monotonicClock,
+ File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
+ super(config, clock, monotonicClock, historyDirectory, handler,
mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class),
mock(UserInfoProvider.class), mockPowerProfile(),
new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index ef0b570a1354..1ff347f93da2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -31,8 +31,6 @@ import android.os.Process;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
@@ -58,18 +56,13 @@ import java.util.Collection;
@SuppressWarnings("GuardedBy")
public class SystemServicePowerCalculatorTest {
@Rule(order = 0)
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Rule(order = 1)
- public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
- ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
- : DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final double PRECISION = 0.000001;
private static final int APP_UID1 = 100;
private static final int APP_UID2 = 200;
- @Rule(order = 2)
+ @Rule(order = 1)
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
.setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 38fe6134d992..d243f92a139f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -408,7 +408,7 @@ public class PowerStatsExporterTest {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
new String[]{"cu570m"},
/* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
- exportAggregatedPowerStats(builder, 3700, 6700);
+ exportAggregatedPowerStats(builder, 3700, 7500);
BatteryUsageStats actual = builder.build();
String message = "Actual BatteryUsageStats: " + actual;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
index f64dc083ca1a..ed3cda0f76ef 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WakelockPowerStatsProcessorTest.java
@@ -34,7 +34,6 @@ import static org.mockito.Mockito.mock;
import android.os.BatteryConsumer;
import android.os.PersistableBundle;
import android.os.Process;
-import android.platform.test.ravenwood.RavenwoodConfig;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.MonotonicClock;
@@ -48,11 +47,6 @@ import org.junit.Rule;
import org.junit.Test;
public class WakelockPowerStatsProcessorTest {
- @RavenwoodConfig.Config
- public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setProvideMainThread(true)
- .build();
-
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
.setAveragePower(PowerProfile.POWER_CPU_IDLE, 720);
diff --git a/services/tests/security/intrusiondetection/Android.bp b/services/tests/security/intrusiondetection/Android.bp
index 00ac90807dff..8d674b14feac 100644
--- a/services/tests/security/intrusiondetection/Android.bp
+++ b/services/tests/security/intrusiondetection/Android.bp
@@ -19,15 +19,20 @@ android_test {
"androidx.test.rules",
"androidx.test.runner",
"compatibility-device-util-axt",
+ "coretests-aidl",
"frameworks-base-testutils",
"junit",
"platform-test-annotations",
+ "servicestests-utils",
"services.core",
"truth",
"Nene",
"Harrier",
"TestApp",
],
+ data: [
+ ":TestIntrusionDetectionApp",
+ ],
platform_apis: true,
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index 39e41cddb662..d58e0d84ee32 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -19,20 +19,16 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
<application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner"/>
- <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver"
- android:permission="android.permission.BIND_DEVICE_ADMIN"
- android:exported="true">
- <meta-data android:name="android.app.device_admin"
- android:resource="@xml/device_admin"/>
- <intent-filter>
- <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
- </intent-filter>
- </receiver>
</application>
+ <queries>
+ <package android:name="com.android.coretests.apps.testapp" />
+ </queries>
+
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.server.security.intrusiondetection.tests"
android:label="Frameworks IntrusionDetection Services Tests"/>
diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
index 42cb9e3236e0..0d211585958a 100644
--- a/services/tests/security/intrusiondetection/AndroidTest.xml
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -20,9 +20,14 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="IntrusionDetectionServiceTests.apk"/>
+ <option name="test-file-name" value="TestIntrusionDetectionApp.apk"/>
<option name="install-arg" value="-t" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop intrusiondetection_service_name com.android.coretests.apps.testapp/.TestLoggingService" />
+ </target_preparer>
+
<option name="test-tag" value="IntrusionDetectionServiceTests" />
<test class="com.android.tradefed.testtype.InstrumentationTest" >
<option name="package" value="com.android.server.security.intrusiondetection.tests" />
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index c185ad5ffd61..5cba6b2c3e67 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -16,12 +16,12 @@
package com.android.server.security.intrusiondetection;
+import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -42,6 +42,9 @@ import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
@@ -52,19 +55,18 @@ import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallba
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
+import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import com.android.bedstead.harrier.BedsteadJUnit4;
-import com.android.bedstead.harrier.annotations.AfterClass;
-import com.android.bedstead.harrier.annotations.BeforeClass;
import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.devicepolicy.DeviceOwner;
-import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.permissions.CommonPermissions;
import com.android.bedstead.permissions.PermissionContext;
import com.android.bedstead.permissions.annotations.EnsureHasPermission;
+import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
import com.android.server.ServiceThread;
import org.junit.Before;
@@ -111,42 +113,26 @@ public class IntrusionDetectionServiceTest {
private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
private DataAggregator mDataAggregator;
private IntrusionDetectionService mIntrusionDetectionService;
+ private IBinder mService;
private TestLooper mTestLooper;
private Looper mLooper;
private TestLooper mTestLooperOfDataAggregator;
private Looper mLooperOfDataAggregator;
private FakePermissionEnforcer mPermissionEnforcer;
-
- @BeforeClass
- public static void setDeviceOwner() {
- ComponentName admin =
- new ComponentName(
- ApplicationProvider.getApplicationContext(),
- IntrusionDetectionAdminReceiver.class);
- try {
- sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
- } catch (NeneException e) {
- fail("Failed to set device owner " + admin.flattenToString() + ": " + e);
- }
- }
-
- @AfterClass
- public static void removeDeviceOwner() {
- try {
- sDeviceOwner.remove();
- } catch (NeneException e) {
- fail("Failed to remove device owner : " + e);
- }
- }
+ private boolean mBoundToLoggingService = false;
+ private static final String TEST_PKG =
+ "com.android.coretests.apps.testapp";
+ private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";
@SuppressLint("VisibleForTests")
@Before
- public void setUp() {
- mContext = spy(ApplicationProvider.getApplicationContext());
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
mPermissionEnforcer = new FakePermissionEnforcer();
mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
mPermissionEnforcer.grant(MANAGE_INTRUSION_DETECTION_STATE);
+ mPermissionEnforcer.grant(BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE);
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
@@ -166,6 +152,7 @@ public class IntrusionDetectionServiceTest {
}
@Test
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testRemoveStateCallback_NoPermission() {
mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE);
StateCallback scb = new StateCallback();
@@ -217,6 +204,7 @@ public class IntrusionDetectionServiceTest {
}
@Test
+ @Ignore("Unit test does not run as system service UID")
public void testRemoveStateCallback() throws RemoteException {
mIntrusionDetectionService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
@@ -227,7 +215,6 @@ public class IntrusionDetectionServiceTest {
assertEquals(STATE_DISABLED, scb1.mState);
assertEquals(STATE_DISABLED, scb2.mState);
- doReturn(true).when(mDataAggregator).initialize();
doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize();
mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);
@@ -240,6 +227,7 @@ public class IntrusionDetectionServiceTest {
assertNull(ccb.mErrorCode);
}
+ @Ignore("Unit test does not run as system service UID")
@Test
public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
mIntrusionDetectionService.setState(STATE_DISABLED);
@@ -400,39 +388,13 @@ public class IntrusionDetectionServiceTest {
}
@Test
- @RequireRunOnSystemUser
- public void testDataSources_Initialize_HasDeviceOwner() throws Exception {
- NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
- SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
-
- assertTrue(networkLogSource.initialize());
- assertTrue(securityLogSource.initialize());
- }
-
- @Test
- @RequireRunOnSystemUser
- public void testDataSources_Initialize_NoDeviceOwner() throws Exception {
- NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
- SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
- ComponentName admin = sDeviceOwner.componentName();
-
- try {
- sDeviceOwner.remove();
- assertFalse(networkLogSource.initialize());
- assertFalse(securityLogSource.initialize());
- } finally {
- sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
- }
- }
-
- @Test
+ @Ignore("Unit test does not run as system service UID")
@RequireRunOnSystemUser
@EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testDataAggregator_AddSecurityEvent() throws Exception {
mIntrusionDetectionService.setState(STATE_ENABLED);
ServiceThread mockThread = spy(ServiceThread.class);
mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
- assertTrue(mDataAggregator.initialize());
// SecurityLogging generates a number of events and callbacks, so create a latch to wait for
// the given event.
@@ -476,12 +438,12 @@ public class IntrusionDetectionServiceTest {
@Test
@RequireRunOnSystemUser
+ @Ignore("Unit test does not run as system service UID")
@EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testDataAggregator_AddNetworkEvent() throws Exception {
mIntrusionDetectionService.setState(STATE_ENABLED);
ServiceThread mockThread = spy(ServiceThread.class);
mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
- assertTrue(mDataAggregator.initialize());
// Network logging may log multiple and callbacks, so create a latch to wait for
// the given event.
@@ -565,6 +527,85 @@ public class IntrusionDetectionServiceTest {
}
}
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void test_StartIntrusionDetectionEventTransportService() {
+ final String TAG = "test_StartIntrusionDetectionEventTransportService";
+ ServiceConnection serviceConnection = null;
+
+ assertEquals(false, mBoundToLoggingService);
+ try {
+ serviceConnection = startTestService();
+ assertEquals(true, mBoundToLoggingService);
+ assertNotNull(serviceConnection);
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException while starting: ", e);
+ fail("Exception thrown while connecting to service");
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException while starting: ", e);
+ fail("Interrupted while connecting to service");
+ } finally {
+ mContext.unbindService(serviceConnection);
+ }
+ }
+
+ private ServiceConnection startTestService() throws SecurityException, InterruptedException {
+ final String TAG = "startTestService";
+ final CountDownLatch latch = new CountDownLatch(1);
+ LocalIntrusionDetectionEventTransport transport =
+ new LocalIntrusionDetectionEventTransport();
+
+ ServiceConnection serviceConnection = new ServiceConnection() {
+ // Called when connection with the service is established.
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mService = transport.getBinder();
+ mBoundToLoggingService = true;
+ latch.countDown();
+ }
+
+ // Called when the connection with the service disconnects unexpectedly.
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "onServiceDisconnected");
+ mBoundToLoggingService = false;
+ }
+ };
+
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+ mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ latch.await(5, TimeUnit.SECONDS);
+
+ // call the methods on the transport object
+ IntrusionDetectionEvent event =
+ new IntrusionDetectionEvent(new SecurityEvent(123, new byte[15]));
+ List<IntrusionDetectionEvent> events = new ArrayList<>();
+ events.add(event);
+ assertTrue(transport.initialize());
+ assertTrue(transport.addData(events));
+ assertTrue(transport.release());
+ assertEquals(1, transport.getEvents().size());
+
+ return serviceConnection;
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void testIntrusionDetectionEventTransportConnection_isValidAndBinds()
+ throws InterruptedException {
+ IntrusionDetectionEventTransportConnection intrusionDetectionEventTransportConnection =
+ new IntrusionDetectionEventTransportConnection(mContext);
+ // In a real scenario, the connection will be initialized by the service.
+ // Just to show that the connection is valid and able to bind,
+ // we initialize it here.
+ assertTrue(intrusionDetectionEventTransportConnection.initialize());
+ }
+
private class MockInjector implements IntrusionDetectionService.Injector {
private final Context mContext;
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
new file mode 100644
index 000000000000..ca5952b140c1
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_platform_security",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "TestIntrusionDetectionApp",
+
+ static_libs: [
+ "frameworks-base-testutils",
+ "services.core",
+ "servicestests-utils",
+ ],
+
+ srcs: ["**/*.java"],
+
+ platform_apis: true,
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
new file mode 100644
index 000000000000..a1a7e29094d2
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.coretests.apps.testapp">
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
+
+ <application>
+ <service android:name=".TestLoggingService"
+ android:exported="true"
+ android:permission="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
+ </application>
+</manifest> \ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
new file mode 100644
index 000000000000..f0012da44fa4
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
@@ -0,0 +1,58 @@
+/*
+ * 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.coretests.apps.testapp;
+
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IntrusionDetectionEventTransport;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that extends {@link IntrusionDetectionEventTransport} to provide a
+ * local transport mechanism for testing purposes. This implementation overrides
+ * the {@link #initialize()}, {@link #addData(List)}, and {@link #release()} methods
+ * to manage events locally within the test environment.
+ *
+ * For now, the implementation returns true for all methods since we don't
+ * have a real data source to send events to.
+ */
+public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport {
+ private List<IntrusionDetectionEvent> mEvents = new ArrayList<>();
+
+ @Override
+ public boolean initialize() {
+ return true;
+ }
+
+ @Override
+ public boolean addData(List<IntrusionDetectionEvent> events) {
+ mEvents.addAll(events);
+ return true;
+ }
+
+ @Override
+ public boolean release() {
+ return true;
+ }
+
+ public List<IntrusionDetectionEvent> getEvents() {
+ return mEvents;
+ }
+} \ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
new file mode 100644
index 000000000000..e4bf987402fd
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.coretests.apps.testapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+
+import com.android.internal.infra.AndroidFuture;
+
+
+public class TestLoggingService extends Service {
+ private static final String TAG = "TestLoggingService";
+ private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport;
+
+ public TestLoggingService() {
+ mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport();
+ }
+
+ // Binder given to clients.
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mLocalIntrusionDetectionEventTransport.getBinder();
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0c058df35195..009ce88cfd6c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -897,16 +897,6 @@ test_module_config {
}
test_module_config {
- name: "FrameworksServicesTests_server_accessibility",
- base: "FrameworksServicesTests",
- test_suites: [
- "automotive-tests",
- "device-tests",
- ],
- include_filters: ["com.android.server.accessibility"],
-}
-
-test_module_config {
name: "FrameworksServicesTests_server_binarytransparencyservicetest",
base: "FrameworksServicesTests",
test_suites: [
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index ae78dfe624c6..cc5be7ebba62 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -40,6 +41,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -50,6 +52,12 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.os.IBinaryTransparencyService;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.After;
import org.junit.Assert;
@@ -68,6 +76,9 @@ import java.util.List;
public class BinaryTransparencyServiceTest {
private static final String TAG = "BinaryTransparencyServiceTest";
+ private static final String TEST_PKG_NAME = "testPackageName";
+ private static final long TEST_VERSION_CODE = 1L;
+
private Context mContext;
private BinaryTransparencyService mBinaryTransparencyService;
private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface;
@@ -83,6 +94,8 @@ public class BinaryTransparencyServiceTest {
private PackageManager mPackageManager;
@Mock
private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private BinaryTransparencyService.BicCallbackHandler.IBicAppInfoHelper mBicAppInfoHelper;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -91,6 +104,9 @@ public class BinaryTransparencyServiceTest {
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
mFaceAuthenticatorsRegisteredCaptor;
+ @Captor
+ private ArgumentCaptor<IBinaryTransparencyService.AppInfo> appInfoCaptor;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -262,4 +278,69 @@ public class BinaryTransparencyServiceTest {
eq("") /* softwareVersion */
);
}
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics() {
+ Bundle data = setupBicCallbackHandlerTest(false,
+ BinaryTransparencyService.MBA_STATUS_NEW_INSTALL);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+ }
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics_for_preloads() {
+ Bundle data = setupBicCallbackHandlerTest(true,
+ BinaryTransparencyService.MBA_STATUS_UPDATED_PRELOAD);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+ }
+
+ @Test
+ public void BicCallbackHandler_uploads_mba_metrics_for_uninstalls() {
+ Bundle data = new Bundle();
+ data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+ TEST_PKG_NAME);
+ data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
+
+ BinaryTransparencyService.BicCallbackHandler handler =
+ new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+ handler.sendResult(data);
+
+ verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+ Assert.assertEquals(TEST_PKG_NAME ,appInfoCaptor.getValue().packageName);
+ Assert.assertEquals(BinaryTransparencyService.MBA_STATUS_UNINSTALLED,
+ appInfoCaptor.getValue().mbaStatus);
+ }
+
+ private Bundle setupBicCallbackHandlerTest(boolean isUpdatedSystemApp,
+ int expectedBtsMbaStatus) {
+ Bundle data = new Bundle();
+ data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+ TEST_PKG_NAME);
+ data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
+ PackageStateInternal mockPackageState = mock(PackageStateInternal.class);
+ when(mPackageManagerInternal.getPackageStateInternal(TEST_PKG_NAME))
+ .thenReturn(mockPackageState);
+ when(mockPackageState.isUpdatedSystemApp()).thenReturn(isUpdatedSystemApp);
+ IBinaryTransparencyService.AppInfo appInfo = new IBinaryTransparencyService.AppInfo();
+ appInfo.packageName = TEST_PKG_NAME;
+ appInfo.longVersion = TEST_VERSION_CODE;
+ when(mBicAppInfoHelper.collectAppInfo(mockPackageState, expectedBtsMbaStatus))
+ .thenReturn(List.of(appInfo));
+ return data;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 27de7644f6b2..a2965b3c51f1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -138,10 +138,12 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.internal.util.reflection.FieldReader;
@@ -259,6 +261,11 @@ public class AccessibilityManagerServiceTest {
mMockA11yController);
when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true);
+ when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(anyInt()))
+ .then(AdditionalAnswers.returnsFirstArg());
+ when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+ eq(UserHandle.USER_CURRENT)))
+ .thenReturn(mTestableContext.getUserId());
final ArrayList<Display> displays = new ArrayList<>();
final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(),
@@ -282,14 +289,21 @@ public class AccessibilityManagerServiceTest {
mInputFilter,
mProxyManager,
mFakePermissionEnforcer);
+ mA11yms.switchUser(mTestableContext.getUserId());
+ mTestableLooper.processAllMessages();
+ FieldSetter.setField(mA11yms,
+ AccessibilityManagerService.class.getDeclaredField("mHasInputFilter"), true);
+ FieldSetter.setField(mA11yms,
+ AccessibilityManagerService.class.getDeclaredField("mInputFilter"), mInputFilter);
final AccessibilityUserState userState = new AccessibilityUserState(
- mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
- mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+ mTestableContext.getUserId(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
mA11yManagerServiceOnDevice = (IAccessibilityManager) new FieldReader(am,
AccessibilityManager.class.getDeclaredField("mService")).read();
FieldSetter.setField(am, AccessibilityManager.class.getDeclaredField("mService"), mA11yms);
+ Mockito.clearInvocations(mMockMagnificationConnectionManager);
}
@After
@@ -652,7 +666,6 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserIdLocked());
userState.setMagnificationCapabilitiesLocked(
ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
- //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
// Invokes client change to trigger onUserStateChanged.
@@ -1025,6 +1038,7 @@ public class AccessibilityManagerServiceTest {
when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+ mTestableLooper.processAllMessages();
assertThat(lockState.get()).containsExactly(false);
}
@@ -1114,9 +1128,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1135,9 +1146,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
@@ -1165,9 +1173,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
@@ -1185,9 +1190,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1231,9 +1233,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1254,9 +1253,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
mA11yms.enableShortcutsForTargets(
@@ -1296,9 +1292,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
throws Exception {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
AccessibilityUtils.setAccessibilityServiceState(
mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1319,9 +1312,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1341,9 +1331,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1362,9 +1349,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
@@ -1384,9 +1368,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1406,9 +1387,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1428,9 +1406,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1450,9 +1425,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
@@ -1478,9 +1450,6 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
- // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
- assumeTrue("The test is setup to run as a user 0",
- isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableQuickSettings_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1684,14 +1653,17 @@ public class AccessibilityManagerServiceTest {
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
broadcastSettingRestored(
ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1707,15 +1679,18 @@ public class AccessibilityManagerServiceTest {
@Test
@DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
String daltonizerTile =
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
String colorInversionTile =
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
broadcastSettingRestored(
ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1729,7 +1704,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
setupShortcutTargetServices();
AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1740,19 +1714,18 @@ public class AccessibilityManagerServiceTest {
ComponentName::getPackageName).toList().toArray(new String[0]);
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
mA11yms.setPackageMonitor(monitor);
assertTrue(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
packages,
- UserHandle.USER_SYSTEM,
+ userState.mUserId,
false
));
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_doIt_packageEnabled_returnsFalse() {
setupShortcutTargetServices();
AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1763,42 +1736,43 @@ public class AccessibilityManagerServiceTest {
ComponentName::getPackageName).toList().toArray(new String[0]);
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
mA11yms.setPackageMonitor(monitor);
assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
packages,
- UserHandle.USER_SYSTEM,
+ userState.mUserId,
true
));
}
@Test
- @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() {
PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
- when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(monitor.getChangingUserId()).thenReturn(mA11yms.getCurrentUserIdLocked());
mA11yms.setPackageMonitor(monitor);
assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
new Intent(),
new String[]{"FOO", "BAR"},
- UserHandle.USER_SYSTEM,
+ mA11yms.getCurrentUserIdLocked(),
false
));
}
@Test
- @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
public void restoreShortcutTargets_hardware_targetsMerged() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
final String otherPrevious = TARGET_MAGNIFICATION;
final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE_NAME;
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
mA11yms.enableShortcutsForTargets(
true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId);
@@ -1815,20 +1789,20 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
// default is present in userState & setting, so it's not cleared
- putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM);
+ putShortcutSettingForUser(HARDWARE, serviceDefault, userState.mUserId);
userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
broadcastSettingRestored(
@@ -1843,10 +1817,10 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
// Restored value from the broadcast contains both default and non-default service.
@@ -1854,8 +1828,8 @@ public class AccessibilityManagerServiceTest {
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
@@ -1870,10 +1844,10 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags({
- android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
- Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() {
+ // TODO: remove the assumption when we fix b/381294327
+ assumeTrue("The test is setup to run as a user 0",
+ mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
// Restored value from the broadcast contains both default and non-default service.
@@ -1881,13 +1855,13 @@ public class AccessibilityManagerServiceTest {
mTestableContext.getOrCreateTestableResources().addOverride(
R.string.config_defaultAccessibilityService, serviceDefault);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
// UserState has default, but setting is null (this emulates a typical scenario in SUW).
userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
- putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM);
+ putShortcutSettingForUser(HARDWARE, null, userState.mUserId);
broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
/*newValue=*/combinedRestored);
@@ -1905,8 +1879,8 @@ public class AccessibilityManagerServiceTest {
public void onNavButtonNavigation_migratesGestureTargets() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1929,20 +1903,20 @@ public class AccessibilityManagerServiceTest {
public void onNavButtonNavigation_gestureTargets_noButtonTargets_navBarButtonMode() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(Set.of(), SOFTWARE);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()), GESTURE);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
mA11yms.updateShortcutsForCurrentNavigationMode();
- assertThat(ShortcutUtils.getButtonMode(mTestableContext, UserHandle.USER_SYSTEM))
+ assertThat(ShortcutUtils.getButtonMode(mTestableContext, userState.mUserId))
.isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
}
@@ -1951,11 +1925,11 @@ public class AccessibilityManagerServiceTest {
public void onGestureNavigation_floatingMenuMode() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -1970,8 +1944,8 @@ public class AccessibilityManagerServiceTest {
public void onNavigation_revertGestureTargets() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(
Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1994,8 +1968,8 @@ public class AccessibilityManagerServiceTest {
public void onNavigation_gestureNavigation_gestureButtonMode_migratesTargetsToGesture() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
userState.updateShortcutTargetsLocked(Set.of(
TARGET_STANDARD_A11Y_SERVICE_NAME,
@@ -2003,7 +1977,7 @@ public class AccessibilityManagerServiceTest {
userState.updateShortcutTargetsLocked(Set.of(), GESTURE);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
mA11yms.updateShortcutsForCurrentNavigationMode();
@@ -2019,11 +1993,11 @@ public class AccessibilityManagerServiceTest {
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void onNavigation_gestureNavigation_correctsButtonMode() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2037,11 +2011,11 @@ public class AccessibilityManagerServiceTest {
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void onNavigation_navBarNavigation_correctsButtonMode() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
setupShortcutTargetServices(userState);
ShortcutUtils.setButtonMode(
- mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+ mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2055,8 +2029,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_navBarNavigationMode_softwareExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2071,8 +2045,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_gestureNavigationMode_softwareExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2087,8 +2061,8 @@ public class AccessibilityManagerServiceTest {
public void showAccessibilityTargetSelection_gestureNavigationMode_gestureExtra() {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2122,18 +2096,19 @@ public class AccessibilityManagerServiceTest {
public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException {
mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
- mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+ int newUserId = mA11yms.getCurrentUserIdLocked() + 1;
+ mA11yms.switchUser(newUserId);
+ mTestableLooper.processAllMessages();
- verify(mUserInitializationCompleteCallback).onUserInitializationComplete(
- UserHandle.MIN_SECONDARY_USER_ID);
+ verify(mUserInitializationCompleteCallback).onUserInitializationComplete(newUserId);
}
@Test
@DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_softwareType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
.isEqualTo(SOFTWARE);
@@ -2143,8 +2118,8 @@ public class AccessibilityManagerServiceTest {
@EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_gestureNavigationMode_gestureType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2156,8 +2131,8 @@ public class AccessibilityManagerServiceTest {
@EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
public void getShortcutTypeForGenericShortcutCalls_buttonNavigationMode_softwareType() {
final AccessibilityUserState userState = new AccessibilityUserState(
- UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
- mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+ mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+ mA11yms.mUserStates.put(userState.mUserId, userState);
Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2348,13 +2323,13 @@ public class AccessibilityManagerServiceTest {
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(
- setting, UserHandle.USER_SYSTEM, str -> str, result);
+ setting, mA11yms.getCurrentUserIdLocked(), str -> str, result);
return result;
}
private void writeStringsToSetting(Set<String> strings, String setting) {
mA11yms.persistColonDelimitedSetToSettingLocked(
- setting, UserHandle.USER_SYSTEM, strings, str -> str);
+ setting, mA11yms.getCurrentUserIdLocked(), strings, str -> str);
}
private void broadcastSettingRestored(String setting, String newValue) {
@@ -2525,10 +2500,6 @@ public class AccessibilityManagerServiceTest {
}
}
- private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
- return service.getCurrentUserIdLocked() == context.getUserId();
- }
-
private void putShortcutSettingForUser(@UserShortcutType int shortcutType,
String shortcutValue, int userId) {
Settings.Secure.putStringForUser(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 8914696d55da..d4f2dcc24af6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -448,4 +448,14 @@ public class AccessibilityServiceConnectionTest {
mConnection.binderDied();
assertThat(mConnection.getServiceInfo().flags & flag).isEqualTo(0);
}
+
+ @Test
+ public void setInputMethodEnabled_checksAccessWithProvidedImeIdAndUserId() {
+ final String imeId = "test_ime_id";
+ final int callingUserId = UserHandle.getCallingUserId();
+ mConnection.setInputMethodEnabled(imeId, true);
+
+ verify(mMockSecurityPolicy).canEnableDisableInputMethod(
+ eq(imeId), any(), eq(callingUserId));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 52b33db556e6..f371823473ef 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -51,9 +51,6 @@ import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.test.FakePermissionEnforcer;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.KeyEvent;
@@ -98,9 +95,6 @@ public class ProxyManagerTest {
private static final int STREAMED_CALLING_UID = 9876;
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private Context mMockContext;
@@ -243,7 +237,6 @@ public class ProxyManagerTest {
* app changes to the proxy device.
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER)
public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
final VirtualDeviceManagerInternal localVdm =
Mockito.mock(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 9cd3186f99f3..605fed09dd9f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -92,6 +93,8 @@ public class AuthServiceTest {
private static final String TEST_OP_PACKAGE_NAME = "test_package";
+ private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
private AuthService mAuthService;
@Rule
@@ -257,12 +260,11 @@ public class AuthServiceTest {
final Binder token = new Binder();
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
token,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -270,7 +272,7 @@ public class AuthServiceTest {
verify(mBiometricService).authenticate(
eq(token),
eq(sessionId),
- eq(userId),
+ eq(mUserId),
eq(mReceiver),
eq(TEST_OP_PACKAGE_NAME),
eq(promptInfo));
@@ -286,12 +288,11 @@ public class AuthServiceTest {
final Binder token = new Binder();
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
token,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -299,7 +300,7 @@ public class AuthServiceTest {
verify(mBiometricService, never()).authenticate(
eq(token),
eq(sessionId),
- eq(userId),
+ eq(mUserId),
eq(mReceiver),
eq(TEST_OP_PACKAGE_NAME),
eq(promptInfo));
@@ -313,12 +314,11 @@ public class AuthServiceTest {
final PromptInfo promptInfo = new PromptInfo();
final long sessionId = 0;
- final int userId = 0;
mAuthService.mImpl.authenticate(
null /* token */,
sessionId,
- userId,
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
promptInfo);
@@ -338,7 +338,7 @@ public class AuthServiceTest {
mAuthService.mImpl.authenticate(
token,
0, /* sessionId */
- 0, /* userId */
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
new PromptInfo());
@@ -356,7 +356,7 @@ public class AuthServiceTest {
mAuthService.mImpl.authenticate(
token,
0, /* sessionId */
- 0, /* userId */
+ mUserId,
mReceiver,
TEST_OP_PACKAGE_NAME,
new PromptInfo());
@@ -414,20 +414,19 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final int expectedResult = BIOMETRIC_SUCCESS;
final int authenticators = 0;
when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt(), anyInt()))
.thenReturn(expectedResult);
final int result = mAuthService.mImpl
- .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
+ .canAuthenticate(TEST_OP_PACKAGE_NAME, mUserId, authenticators);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).canAuthenticate(
eq(TEST_OP_PACKAGE_NAME),
- eq(userId),
+ eq(mUserId),
eq(UserHandle.getCallingUserId()),
eq(authenticators));
}
@@ -440,18 +439,17 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final boolean expectedResult = true;
when(mBiometricService.hasEnrolledBiometrics(anyInt(), anyString())).thenReturn(
expectedResult);
- final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(userId,
+ final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(mUserId,
TEST_OP_PACKAGE_NAME);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).hasEnrolledBiometrics(
- eq(userId),
+ eq(mUserId),
eq(TEST_OP_PACKAGE_NAME));
}
@@ -528,13 +526,12 @@ public class AuthServiceTest {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
- final int userId = 0;
final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
- mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+ mAuthService.mImpl.getLastAuthenticationTime(mUserId, authenticators);
waitForIdle();
- verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+ verify(mBiometricService).getLastAuthenticationTime(eq(mUserId), eq(authenticators));
}
private static void setInternalAndTestBiometricPermissions(
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 1a593dd9baba..42b7f4bcda7b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -755,6 +755,7 @@ public class GenericWindowPolicyControllerTest {
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+ verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
verify(mActivityListener, never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
@@ -776,6 +777,10 @@ public class GenericWindowPolicyControllerTest {
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+
+ assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+ verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onSecureWindowHidden(eq(DISPLAY_ID));
}
@Test
@@ -794,6 +799,7 @@ public class GenericWindowPolicyControllerTest {
verify(mActivityListener, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+ verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
verify(mActivityListener, never())
.onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 727d1b59646a..32578a7dc10f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -2076,10 +2076,10 @@ public class VirtualDeviceManagerServiceTest {
private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
CharSequence displayName) {
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
- /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
+ MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
/* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
- /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
+ /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null, /* deviceId= */ null);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index ab5a5a9bf2fe..5127b2d11e44 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -24,9 +24,14 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateRequest;
@@ -34,6 +39,7 @@ import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -336,6 +342,53 @@ public final class DeviceStateManagerServiceTest {
}
@Test
+ public void halRegisterUnregisterCallback() throws RemoteException {
+ IDeviceStateService halService = mService.getHalBinderService();
+ IDeviceStateListener halListener = new IDeviceStateListener.Stub() {
+ @Override
+ public void onDeviceStateChanged(DeviceStateConfiguration deviceState) { }
+
+ @Override
+ public int getInterfaceVersion() {
+ return IDeviceStateListener.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return IDeviceStateListener.HASH;
+ }
+ };
+
+ int errorCode = ErrorCode.OK;
+ try {
+ halService.unregisterListener(halListener);
+ } catch(ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+ errorCode = ErrorCode.OK;
+ try {
+ halService.unregisterListener(null);
+ } catch(ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+ halService.registerListener(halListener);
+
+ errorCode = ErrorCode.OK;
+ try {
+ halService.registerListener(halListener);
+ } catch (ServiceSpecificException e) {
+ errorCode = e.errorCode;
+ }
+ assertEquals(errorCode, ErrorCode.ALREADY_EXISTS);
+
+ halService.unregisterListener(halListener);
+ }
+
+ @Test
public void registerCallback() throws RemoteException {
final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index c7574bdc3f6c..30dac9f3813d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -41,11 +41,14 @@ import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.tv.flags.Flags;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.stats.hdmi.HdmiStatsEnums;
import androidx.test.InstrumentationRegistry;
@@ -54,6 +57,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.SystemService;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -83,6 +87,9 @@ public class HdmiCecAtomLoggingTest {
private HdmiEarcController mHdmiEarcController;
private FakeEarcNativeWrapper mEarcNativeWrapper;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws RemoteException {
mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
@@ -232,7 +239,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.VOLUME_MUTE,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -258,7 +266,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -285,7 +294,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
Constants.MESSAGE_RECORD_ON,
- HdmiStatsEnums.UNRECOGNIZED_OPCODE);
+ HdmiStatsEnums.UNRECOGNIZED_OPCODE,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -311,7 +321,8 @@ public class HdmiCecAtomLoggingTest {
HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
- HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
}
@Test
@@ -337,6 +348,59 @@ public class HdmiCecAtomLoggingTest {
}
@Test
+ @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+ public void testMessageReported_writesAtom_reportPhysicalAddress() {
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_PLAYBACK_1, 0x1234, HdmiDeviceInfo.DEVICE_PLAYBACK);
+
+ mHdmiCecAtomWriterSpy.messageReported(
+ message,
+ HdmiStatsEnums.INCOMING,
+ 1234);
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .writeHdmiCecMessageReportedAtom(
+ 1234,
+ HdmiStatsEnums.INCOMING,
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+ HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+ HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ 0x1234);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+ public void testMessageReported_writesAtom_reportPhysicalAddress_noParams() {
+ HdmiCecMessage message = HdmiCecMessage.build(
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ new byte[0]);
+
+ mHdmiCecAtomWriterSpy.messageReported(
+ message,
+ HdmiStatsEnums.INCOMING,
+ 1234);
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .writeHdmiCecMessageReportedAtom(
+ 1234,
+ HdmiStatsEnums.INCOMING,
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_BROADCAST,
+ Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+ HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+ HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+ HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+ HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+ HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
+ }
+
+ @Test
public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
@@ -369,4 +433,21 @@ public class HdmiCecAtomLoggingTest {
.dsmStatusChanged(anyBoolean(), anyBoolean(),
eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
}
+
+ @Test
+ public void testPowerStateChangeOnActiveSourceLostToggled_writesAtom_logReasonSetting() {
+ mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.writePowerStateChangeOnActiveSourceLostAtom(true);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING), anyString(), anyInt(), anyInt());
+ verify(mHdmiCecAtomWriterSpy, never())
+ .powerStateChangeOnActiveSourceLostChanged(eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP), anyString(), anyInt(), anyInt());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 077bb03c8359..861e72d4ac79 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -95,9 +95,6 @@ public class HdmiCecLocalDevicePlaybackTest {
private boolean mActiveMediaSessionsPaused;
private FakePowerManagerInternalWrapper mPowerManagerInternal =
new FakePowerManagerInternalWrapper();
-
- private boolean mIsOnActiveSourceLostPopupActive;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -165,12 +162,12 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@Override
void startHdmiCecActiveSourceLostActivity() {
- mIsOnActiveSourceLostPopupActive = true;
+ setIsActiveSourceLostPopupLaunched(true);
}
@Override
void dismissUiOnActiveSourceStatusRecovered() {
- mIsOnActiveSourceLostPopupActive = false;
+ setIsActiveSourceLostPopupLaunched(false);
}
};
mHdmiCecLocalDevicePlayback.init();
@@ -2389,7 +2386,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
}
@Test
@@ -2430,7 +2427,7 @@ public class HdmiCecLocalDevicePlaybackTest {
// Pop-up is not shown, playback device asserts active source since TV doesn't answer the
// request.
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2480,7 +2477,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// Pop-up is not shown since playback device is active source.
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2532,7 +2529,7 @@ public class HdmiCecLocalDevicePlaybackTest {
// Pop-up is shown, playback device doesn't assert active source since active path is
// switched to a non-CEC device.
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(ADDR_INVALID);
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
@@ -2569,7 +2566,7 @@ public class HdmiCecLocalDevicePlaybackTest {
});
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
@@ -2609,13 +2606,13 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.onCecMessage(activeSourceFromTv);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2664,13 +2661,13 @@ public class HdmiCecLocalDevicePlaybackTest {
// Pop-up is triggered.
mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(routingChangeToPlayback))
.isEqualTo(Constants.HANDLED);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2711,7 +2708,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.onCecMessage(activeSourceFromTv);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
mHdmiControlService.oneTouchPlay(new IHdmiControlCallback() {
@Override
public void onComplete(int result) throws RemoteException {
@@ -2724,7 +2721,7 @@ public class HdmiCecLocalDevicePlaybackTest {
});
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
.isEqualTo(mPlaybackLogicalAddress);
@@ -2897,11 +2894,11 @@ public class HdmiCecLocalDevicePlaybackTest {
} else {
mTestLooper.moveTimeForward(TIMEOUT_MS);
mTestLooper.dispatchAll();
- assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
return;
}
}
- assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
mPowerManagerInternal.setIdleDuration(idleDuration);
mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
deleted file mode 100644
index fd221185bacf..000000000000
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity;
-
-import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
-import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
-import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
-import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
-
-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.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-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 static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.R;
-import com.android.server.compat.PlatformCompat;
-import com.android.server.testutils.TestUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
-@RunWith(JUnit4.class)
-public class AppIntegrityManagerServiceImplTest {
- private static final String TEST_APP_PATH =
- "AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk";
-
- private static final String TEST_APP_TWO_CERT_PATH =
- "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
-
- private static final String TEST_APP_SOURCE_STAMP_PATH =
- "AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk";
-
- private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
- private static final String VERSION = "version";
- private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
-
- private static final String PACKAGE_NAME = "com.test.app";
-
- private static final long VERSION_CODE = 100;
- private static final String INSTALLER = "com.long.random.test.installer.name";
-
- // These are obtained by running the test and checking logcat.
- private static final String APP_CERT =
- "F14CFECF5070874C05D3D2FA98E046BE20BDE02A0DC74BAF6B59C6A0E4C06850";
- // We use SHA256 for package names longer than 32 characters.
- private static final String INSTALLER_SHA256 =
- "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
- private static final String SOURCE_STAMP_CERTIFICATE_HASH =
- "C6E737809CEF2B08CC6694892215F82A5E8FBC3C2A0F6212770310B90622D2D9";
-
- private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
- "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
- private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
- "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
-
- private static final String PLAY_STORE_PKG = "com.android.vending";
- private static final String ADB_INSTALLER = "adb";
- private static final String PLAY_STORE_CERT = "play_store_cert";
-
- @org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock PackageManagerInternal mPackageManagerInternal;
- @Mock PlatformCompat mPlatformCompat;
- @Mock Context mMockContext;
- @Mock Resources mMockResources;
- @Mock Handler mHandler;
-
- private final Context mRealContext = InstrumentationRegistry.getTargetContext();
-
- private PackageManager mSpyPackageManager;
- private File mTestApk;
- private File mTestApkTwoCerts;
- private File mTestApkSourceStamp;
-
- // under test
- private AppIntegrityManagerServiceImpl mService;
-
- @Before
- public void setup() throws Exception {
- mTestApk = File.createTempFile("AppIntegrity", ".apk");
- try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_PATH)) {
- Files.copy(inputStream, mTestApk.toPath(), REPLACE_EXISTING);
- }
-
- mTestApkTwoCerts = File.createTempFile("AppIntegrityTwoCerts", ".apk");
- try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
- Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
- }
-
- mTestApkSourceStamp = File.createTempFile("AppIntegritySourceStamp", ".apk");
- try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_SOURCE_STAMP_PATH)) {
- Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING);
- }
-
- mService =
- new AppIntegrityManagerServiceImpl(
- mMockContext,
- mPackageManagerInternal,
- mHandler);
-
- mSpyPackageManager = spy(mRealContext.getPackageManager());
- // setup mocks to prevent NPE
- when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
- when(mMockContext.getResources()).thenReturn(mMockResources);
- when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
- // These are needed to override the Settings.Global.get result.
- when(mMockContext.getContentResolver()).thenReturn(mRealContext.getContentResolver());
- setIntegrityCheckIncludesRuleProvider(true);
- }
-
- @After
- public void tearDown() throws Exception {
- mTestApk.delete();
- mTestApkTwoCerts.delete();
- mTestApkSourceStamp.delete();
- }
-
- @Test
- public void broadcastReceiverRegistration() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp();
- ArgumentCaptor<IntentFilter> intentFilterCaptor =
- ArgumentCaptor.forClass(IntentFilter.class);
-
- verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any());
- assertEquals(1, intentFilterCaptor.getValue().countActions());
- assertEquals(
- Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION,
- intentFilterCaptor.getValue().getAction(0));
- assertEquals(1, intentFilterCaptor.getValue().countDataTypes());
- assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0));
- }
-
- @Test
- public void handleBroadcast_allow() throws Exception {
- allowlistUsAsRuleProvider();
- makeUsSystemApp();
- ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mMockContext)
- .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
- Intent intent = makeVerificationIntent();
-
- broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
- runJobInHandler();
-
- verify(mPackageManagerInternal)
- .setIntegrityVerificationResult(
- 1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- }
-
- private void allowlistUsAsRuleProvider() {
- Resources mockResources = mock(Resources.class);
- when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
- .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
- when(mMockContext.getResources()).thenReturn(mockResources);
- }
-
- private void runJobInHandler() {
- ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
- // sendMessageAtTime is the first non-final method in the call chain when "post" is invoked.
- verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
- messageCaptor.getValue().getCallback().run();
- }
-
- private void makeUsSystemApp() throws Exception {
- makeUsSystemApp(true);
- }
-
- private void makeUsSystemApp(boolean isSystemApp) throws Exception {
- PackageInfo packageInfo =
- mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
- if (isSystemApp) {
- packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- } else {
- packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
- }
- doReturn(packageInfo)
- .when(mSpyPackageManager)
- .getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
- when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
- }
-
- private Intent makeVerificationIntent() throws Exception {
- PackageInfo packageInfo =
- mRealContext
- .getPackageManager()
- .getPackageInfo(
- TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES);
- doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt());
- doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt());
- doReturn(new String[]{INSTALLER}).when(mSpyPackageManager).getPackagesForUid(anyInt());
- return makeVerificationIntent(INSTALLER);
- }
-
- private Intent makeVerificationIntent(String installer) throws Exception {
- Intent intent = new Intent();
- intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE);
- intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
- intent.putExtra(EXTRA_VERIFICATION_ID, 1);
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
- intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, installer);
- intent.putExtra(
- EXTRA_VERIFICATION_INSTALLER_UID,
- mMockContext.getPackageManager().getPackageUid(installer, /* flags= */ 0));
- intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, VERSION_CODE);
- return intent;
- }
-
- private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception {
- int value = shouldInclude ? 1 : 0;
- Settings.Global.putInt(
- mRealContext.getContentResolver(),
- Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
- value);
- assertThat(
- Settings.Global.getInt(
- mRealContext.getContentResolver(),
- Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
- -1)
- == 1)
- .isEqualTo(shouldInclude);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index fac5c1f94e56..71a05f3b8509 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -40,11 +40,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.Range;
+import com.android.internal.R;
import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
@@ -87,6 +89,7 @@ public final class ShareTargetPredictorTest {
private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private DataManager mDataManager;
@Mock private Consumer<List<AppTarget>> mUpdatePredictionsMethod;
@Mock private PackageData mPackageData1;
@@ -116,11 +119,14 @@ public final class ShareTargetPredictorTest {
when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
- when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+ when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
Context.APP_PREDICTION_SERVICE);
when(mContext.getSystemService(AppPredictionManager.class))
.thenReturn(new AppPredictionManager(mContext));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getString(R.string.config_chooserActivity))
+ .thenReturn("com.android.intentresolver/.ChooserActivity");
Bundle bundle = new Bundle();
bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
@@ -280,6 +286,25 @@ public final class ShareTargetPredictorTest {
}
@Test
+ public void testPredictTargets_emptyIntentFilter() {
+ Bundle bundle = new Bundle();
+ IntentFilter filter = new IntentFilter();
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, filter);
+ AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
+ .setUiSurface(UI_SURFACE_SHARE)
+ .setPredictedTargetCount(NUM_PREDICTED_TARGETS)
+ .setExtras(bundle)
+ .build();
+ mPredictor = new ShareTargetPredictor(
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+
+ mPredictor.predictTargets();
+
+ verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture());
+ assertThat(mAppTargetCaptor.getValue()).isEmpty();
+ }
+
+ @Test
public void testPredictTargets_noSharingHistoryRankedByShortcutRank() {
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3));
mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2));
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 8290e1cfb9db..5862ac65eba9 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -16,11 +16,21 @@
package com.android.server.supervision
+import android.app.Activity
+import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManagerInternal
+import android.app.supervision.flags.Flags
+import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.os.Handler
import android.os.PersistableBundle
+import android.os.UserHandle
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,22 +56,21 @@ import org.mockito.kotlin.whenever
*/
@RunWith(AndroidJUnit4::class)
class SupervisionServiceTest {
- companion object {
- const val USER_ID = 100
- }
-
- @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+ @Mock private lateinit var mockPackageManager: PackageManager
@Mock private lateinit var mockUserManagerInternal: UserManagerInternal
private lateinit var context: Context
+ private lateinit var lifecycle: SupervisionService.Lifecycle
private lateinit var service: SupervisionService
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
+ context = SupervisionContextWrapper(context, mockPackageManager)
LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -70,48 +79,91 @@ class SupervisionServiceTest {
LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
service = SupervisionService(context)
+ lifecycle = SupervisionService.Lifecycle(context, service)
+ lifecycle.registerProfileOwnerListener()
}
@Test
- @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
- fun syncStateWithDevicePolicyManager_supervisionAppIsProfileOwner_enablesSupervision() {
- val supervisionPackageName =
- context.getResources().getString(R.string.config_systemSupervision)
-
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_supervisionAppIsProfileOwner_enablesSupervision() {
whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
- .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
- service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+ simulateUserStarting(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
}
@Test
- @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
- fun syncStateWithDevicePolicyManager_userPreCreated_doesNotEnableSupervision() {
- val supervisionPackageName =
- context.getResources().getString(R.string.config_systemSupervision)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_userPreCreated_doesNotEnableSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+ simulateUserStarting(USER_ID, preCreated = true)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun onUserStarting_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
- .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
- service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID, preCreated = true))
+ simulateUserStarting(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
}
@Test
- @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
- fun syncStateWithDevicePolicyManager_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsProfileOwner_enablesSupervision() {
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
.thenReturn(ComponentName("other.package", "MainActivity"))
- service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+ broadcastProfileOwnerChanged(USER_ID)
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
}
@Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, true)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, false)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_notSupervisionUid_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID)).thenReturn(arrayOf())
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
fun setSupervisionEnabledForUser() {
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
@@ -150,9 +202,66 @@ class SupervisionServiceTest {
assertThat(userData.supervisionLockScreenOptions).isNull()
}
- private fun newTargetUser(userId: Int, preCreated: Boolean = false): TargetUser {
+ private val systemSupervisionPackage: String
+ get() = context.getResources().getString(R.string.config_systemSupervision)
+
+ private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
userInfo.preCreated = preCreated
- return TargetUser(userInfo)
+ lifecycle.onUserStarting(TargetUser(userInfo))
+ }
+
+ private fun broadcastProfileOwnerChanged(userId: Int) {
+ val intent = Intent(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED)
+ context.sendBroadcastAsUser(intent, UserHandle.of(userId))
+ }
+
+ private companion object {
+ const val USER_ID = 100
+ val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+ }
+}
+
+/**
+ * A context wrapper that allows broadcast intents to immediately invoke the receivers without
+ * performing checks on the sending user.
+ */
+private class SupervisionContextWrapper(val context: Context, val pkgManager: PackageManager) :
+ ContextWrapper(context) {
+ val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
+
+ override fun getPackageManager() = pkgManager
+
+ override fun registerReceiverForAllUsers(
+ receiver: BroadcastReceiver?,
+ filter: IntentFilter,
+ broadcastPermission: String?,
+ scheduler: Handler?,
+ ): Intent? {
+ if (receiver != null) {
+ interceptors.add(Pair(receiver, filter))
+ }
+ return null
+ }
+
+ override fun sendBroadcastAsUser(intent: Intent, user: UserHandle) {
+ val pendingResult =
+ BroadcastReceiver.PendingResult(
+ Activity.RESULT_OK,
+ /* resultData= */ "",
+ /* resultExtras= */ null,
+ /* type= */ 0,
+ /* ordered= */ true,
+ /* sticky= */ false,
+ /* token= */ null,
+ user.identifier,
+ /* flags= */ 0,
+ )
+ for ((receiver, filter) in interceptors) {
+ if (filter.match(contentResolver, intent, false, "") > 0) {
+ receiver.setPendingResult(pendingResult)
+ receiver.onReceive(context, intent)
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 5852af780b8b..d20f73d3c834 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -506,9 +506,9 @@ public class TunerResourceManagerServiceTest {
assertThat(client0.getProfile().getInUseFrontendHandles())
.isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle)));
- // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
- // (client0) to maintain ownership such as requester will not get the resources.
- client1.getProfile().setResourceHolderRetain(true);
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
@@ -520,10 +520,10 @@ public class TunerResourceManagerServiceTest {
.isEqualTo(client0.getId());
assertThat(client0.isReclaimed()).isFalse();
- // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
// Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
// resources.
- client1.getProfile().setResourceHolderRetain(false);
+ client1.getProfile().setResourceOwnershipRetention(false);
assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
.isTrue();
@@ -645,9 +645,9 @@ public class TunerResourceManagerServiceTest {
.isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
- // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
- // to maintain ownership such as requester (client1) will not get the resources.
- client1.getProfile().setResourceHolderRetain(true);
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder to maintain ownership such as requester (client1) will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
request = casSessionRequest(client1.getId(), 1);
assertThat(
@@ -663,10 +663,10 @@ public class TunerResourceManagerServiceTest {
assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
assertThat(client0.isReclaimed()).isFalse();
- // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
// Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
// resources.
- client1.getProfile().setResourceHolderRetain(false);
+ client1.getProfile().setResourceOwnershipRetention(false);
assertThat(
mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
@@ -759,9 +759,9 @@ public class TunerResourceManagerServiceTest {
.isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
- // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
- // (client0) to maintain ownership such as requester will not get the resources.
- client1.getProfile().setResourceHolderRetain(true);
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
request = tunerCiCamRequest(client1.getId(), 1);
assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
@@ -776,10 +776,10 @@ public class TunerResourceManagerServiceTest {
assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
assertThat(client0.isReclaimed()).isFalse();
- // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
// Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
// resources.
- client1.getProfile().setResourceHolderRetain(false);
+ client1.getProfile().setResourceOwnershipRetention(false);
assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
.isTrue();
@@ -927,9 +927,9 @@ public class TunerResourceManagerServiceTest {
assertThat(client0.getProfile().getInUseLnbHandles())
.isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
- // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
- // (client0) to maintain ownership such as requester will not get the resources.
- client1.getProfile().setResourceHolderRetain(true);
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+ // Holder (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceOwnershipRetention(true);
request = new TunerLnbRequest();
request.clientId = client1.getId();
@@ -942,10 +942,10 @@ public class TunerResourceManagerServiceTest {
assertThat(client0.isReclaimed()).isFalse();
assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
- // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
// Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
// resources.
- client1.getProfile().setResourceHolderRetain(false);
+ client1.getProfile().setResourceOwnershipRetention(false);
assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index aee9f0f3b880..13a6e4cc7b30 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
public class TestSystemImpl implements SystemInterface {
private String mUserProvider = null;
@@ -36,6 +37,7 @@ public class TestSystemImpl implements SystemInterface {
Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
private final int mNumRelros;
private final boolean mIsDebuggable;
+ private Predicate<PackageInfo> mCompatibilityPredicate;
public static final int PRIMARY_USER_ID = 0;
@@ -45,6 +47,7 @@ public class TestSystemImpl implements SystemInterface {
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
mUsers.add(PRIMARY_USER_ID);
+ mCompatibilityPredicate = pi -> true;
}
public void addUser(int userId) {
@@ -129,6 +132,15 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
+ public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+ return mCompatibilityPredicate.test(packageInfo);
+ }
+
+ public void setCompatibilityPredicate(Predicate<PackageInfo> predicate) {
+ mCompatibilityPredicate = predicate;
+ }
+
+ @Override
public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo info) {
Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
List<UserPackage> ret = new ArrayList();
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 42eb60958d53..bf99b6af2345 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -25,10 +25,12 @@ import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Base64;
-import android.webkit.UserPackage;
+import android.webkit.Flags;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
@@ -175,8 +177,6 @@ public class WebViewUpdateServiceTest {
// no flag means invalid
p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah");
}
- // Default to this package being valid in terms of targetSdkVersion.
- p.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
return p;
}
@@ -1238,20 +1238,21 @@ public class WebViewUpdateServiceTest {
}
/**
- * Ensure that packages with a targetSdkVersion targeting the current platform are valid, and
+ * Ensure that packages with a targetSdkVersion targeting the correct platform are valid, and
* that packages targeting an older version are not valid.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_USE_B_ENTRY_POINT)
public void testTargetSdkVersionValidity() {
PackageInfo newSdkPackage = createPackageInfo("newTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ true /* enabled */, true /* valid */, true /* installed */);
+ newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK;
+ true /* enabled */, true /* valid */, true /* installed */);
+ currentSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage",
- true /* enabled */, true /* valid */, true /* installed */);
- oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1;
+ true /* enabled */, true /* valid */, true /* installed */);
+ oldSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.S;
WebViewProviderInfo newSdkProviderInfo =
new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
@@ -1264,6 +1265,9 @@ public class WebViewUpdateServiceTest {
newSdkProviderInfo
};
setupWithPackages(packages);
+ // Mock the compatibility predicate, requiring T as targetSdkVersion.
+ mTestSystemImpl.setCompatibilityPredicate(
+ pi -> pi.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU);
// Start with the setting pointing to the invalid package
mTestSystemImpl.updateUserSetting(oldSdkPackage.packageName);
@@ -1280,6 +1284,54 @@ public class WebViewUpdateServiceTest {
1 /* first preparation phase */);
}
+ /**
+ * Ensure that packages with a versionCode new enough for the current platform are valid, and
+ * that older packages are not valid.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_USE_B_ENTRY_POINT)
+ public void testVersionCodeOSCompatValidity() {
+ PackageInfo newVersionPackage = createPackageInfo("newVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ newVersionPackage.setLongVersionCode(200L);
+ PackageInfo currentVersionPackage = createPackageInfo("currentVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ currentVersionPackage.setLongVersionCode(100L);
+ PackageInfo oldVersionPackage = createPackageInfo("oldVersionPackage",
+ true /* enabled */, true /* valid */, true /* installed */);
+ oldVersionPackage.setLongVersionCode(50L);
+
+ WebViewProviderInfo newVersionProviderInfo =
+ new WebViewProviderInfo(newVersionPackage.packageName, "", true, false, null);
+ WebViewProviderInfo currentVersionProviderInfo =
+ new WebViewProviderInfo(currentVersionPackage.packageName, "", true, false, null);
+ WebViewProviderInfo[] packages =
+ new WebViewProviderInfo[] {
+ currentVersionProviderInfo,
+ new WebViewProviderInfo(oldVersionPackage.packageName, "", true, false, null),
+ newVersionProviderInfo
+ };
+ setupWithPackages(packages);
+ // Mock the compatibility predicate as requiring 100 as versionCode.
+ mTestSystemImpl.setCompatibilityPredicate(
+ pi -> pi.getLongVersionCode() >= 100L);
+ // Start with the setting pointing to the invalid package
+ mTestSystemImpl.updateUserSetting(oldVersionPackage.packageName);
+
+ mTestSystemImpl.setPackageInfo(newVersionPackage);
+ mTestSystemImpl.setPackageInfo(currentVersionPackage);
+ mTestSystemImpl.setPackageInfo(oldVersionPackage);
+
+ assertArrayEquals(
+ new WebViewProviderInfo[] { currentVersionProviderInfo, newVersionProviderInfo },
+ mWebViewUpdateServiceImpl.getValidWebViewPackages());
+
+ runWebViewBootPreparationOnMainSync();
+
+ checkPreparationPhasesForPackage(currentVersionPackage.packageName,
+ 1 /* first preparation phase */);
+ }
+
@Test
public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
String nonDefaultPackage = "nonDefaultPackage";
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 3b0cb4ad8779..c4b8599a483c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -118,6 +118,7 @@ import org.mockito.Spy;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -232,7 +233,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
any(AlarmManager.OnAlarmListener.class), any(Handler.class));
doAnswer(inv -> {
- mCustomListener = () -> {};
+ mCustomListener = () -> {
+ };
return null;
}).when(mAlarmManager).cancel(eq(mCustomListener));
when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
@@ -1321,7 +1323,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void enableCarMode_failsForBogusPackageName() throws Exception {
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
assertThrows(SecurityException.class, () -> mService.enableCarMode(0, 0, PACKAGE_NAME));
assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
@@ -1343,19 +1345,19 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void disableCarMode_failsForBogusPackageName() throws Exception {
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID);
mService.enableCarMode(0, 0, PACKAGE_NAME);
assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
assertThrows(SecurityException.class,
- () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
+ () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
// Clean up
when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
- .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+ .thenReturn(TestInjector.DEFAULT_CALLING_UID);
mService.disableCarModeByCallingPackage(0, PACKAGE_NAME);
assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
}
@@ -1460,9 +1462,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
- private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+ // Test the attention mode overlay with all the possible attention modes and the initial night
+ // mode state. Also tests if the attention mode is turned off when the night mode is toggled by
+ // the user.
+ private void testAttentionModeThemeOverlay(boolean initialNightMode) throws RemoteException {
//setup
- if (modeNight) {
+ if (initialNightMode) {
mService.setNightMode(MODE_NIGHT_YES);
assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
} else {
@@ -1470,23 +1475,32 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
}
- // attention modes with expected night modes
- Map<Integer, Boolean> modes = Map.of(
- MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
- MODE_ATTENTION_THEME_OVERLAY_DAY, false,
- MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
- );
+ // Attention modes with expected night modes.
+ // Important to keep modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode) in the
+ // first position, hence LinkedHashMap.
+ Map<Integer, Boolean> modes = new LinkedHashMap<>();
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode);
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_DAY, false);
+ modes.put(MODE_ATTENTION_THEME_OVERLAY_NIGHT, true);
// test
- for (int aMode : modes.keySet()) {
+ for (int attentionMode : modes.keySet()) {
try {
- mService.setAttentionModeThemeOverlay(aMode);
+ mService.setAttentionModeThemeOverlay(attentionMode);
int appliedAMode = mService.getAttentionModeThemeOverlay();
- boolean nMode = modes.get(aMode);
-
- assertEquals(aMode, appliedAMode);
- assertEquals(isNightModeActivated(), nMode);
+ boolean expectedNightMode = modes.get(attentionMode);
+
+ assertEquals(attentionMode, appliedAMode);
+ assertEquals(expectedNightMode, isNightModeActivated());
+
+ // If attentionMode is active, flip the night mode and assets
+ // the attention mode is disabled
+ if (attentionMode != MODE_ATTENTION_THEME_OVERLAY_OFF) {
+ mService.setNightModeActivated(!expectedNightMode);
+ assertEquals(MODE_ATTENTION_THEME_OVERLAY_OFF,
+ mService.getAttentionModeThemeOverlay());
+ }
} catch (RemoteException e) {
fail("Error communicating with server: " + e.getMessage());
}
@@ -1551,7 +1565,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
private final int callingUid;
public TestInjector() {
- this(DEFAULT_CALLING_UID);
+ this(DEFAULT_CALLING_UID);
}
public TestInjector(int callingUid) {
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 dd278fccad14..6cb24293a7d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2338,6 +2338,177 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testUpdateToUngroupableSection_cleanupUngrouped() {
+ final String pkg = "package";
+ // Post notification w/o group in a valid section
+ NotificationRecord notification = spy(getNotificationRecord(pkg, 0, "", mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, 0, "0", UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+
+ // Update notification to invalid section
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+ assertThat(GroupHelper.getSection(notification)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notification, false);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that GH internal state (ungrouped list) was cleaned-up
+ // Post AUTOGROUP_AT_COUNT-1 notifications => should not autogroup
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ int id = 42 + i;
+ notification = getNotificationRecord(pkg, id, "" + id, mUser,
+ null, false, IMPORTANCE_LOW);
+ mGroupHelper.onNotificationPosted(notification, false);
+ }
+
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testUpdateToUngroupableSection_afterAutogroup_isUngrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notification w/o group in a valid section
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+ notificationList.add(notification);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that the updated notification was removed from the autogroup
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testUpdateToUngroupableSection_onRemoved_isUngrouped() {
+ final String pkg = "package";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ // Post notification w/o group in a valid section
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+ "", false, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ mGroupHelper.onNotificationPosted(notification, false);
+ notificationList.add(notification);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section and removed it
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ notificationList.remove(notifToInvalidate);
+ mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+
+ // Check that the autogroup was updated
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+ public void testUpdateToUngroupableSection_afterForceGrouping_isUngrouped() {
+ final String pkg = "package";
+ final String groupName = "testGroup";
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ // Post valid section summary notifications without children => force group
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord notification = spy(getNotificationRecord(mPkg, i, "" + i, mUser,
+ groupName, true, IMPORTANCE_LOW));
+ Notification n = mock(Notification.class);
+ StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM, groupName));
+ when(notification.getNotification()).thenReturn(n);
+ when(notification.getSbn()).thenReturn(sbn);
+ when(n.getGroup()).thenReturn(groupName);
+ when(sbn.getNotification()).thenReturn(n);
+ when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+ assertThat(GroupHelper.getSection(notification)).isNotNull();
+ notificationList.add(notification);
+ mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+ summaryByGroup);
+ }
+
+ final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey), eq(true));
+
+ // Update a notification to invalid section
+ Mockito.reset(mCallback);
+ final NotificationRecord notifToInvalidate = notificationList.get(0);
+ when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+ true);
+ assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+ boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+ assertThat(needsAutogrouping).isFalse();
+
+ // Check that GH internal state (ungrouped list) was cleaned-up
+ verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+ eq(expectedGroupKey), any());
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel() {
final String pkg = "package";
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 863f42f3d1c1..074cbb57d5b7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -49,10 +49,14 @@ import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
+import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -369,6 +373,7 @@ import java.io.FileOutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -6884,10 +6889,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testReadPolicyXml_backupRestoreLogging() throws Exception {
BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
+ if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
+ // By default, the ZenModeHelper only has a configuration for the system user.
+ // If the current user is not the system user, the user must be updated.
+ mService.mZenModeHelper.onUserSwitched(ActivityManager.getCurrentUser());
+ }
UserInfo ui = new UserInfo(ActivityManager.getCurrentUser(), "Clone", UserInfo.FLAG_FULL);
ui.userType = USER_TYPE_FULL_SYSTEM;
when(mUmInternal.getUserInfo(ActivityManager.getCurrentUser())).thenReturn(ui);
- when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(new ArrayMap<>());
+ when(mPermissionHelper.getNotificationPermissionValues(ActivityManager.getCurrentUser()))
+ .thenReturn(new ArrayMap<>());
TypedXmlSerializer serializer = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
@@ -8931,8 +8942,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testAreBubblesEnabled_false() throws Exception {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_BUBBLES, 0);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BUBBLES, 0, UserHandle.getUserId(mUid));
mService.mPreferencesHelper.updateBubblesEnabled();
assertFalse(mBinderService.areBubblesEnabled(UserHandle.getUserHandleForUid(mUid)));
}
@@ -11267,19 +11278,71 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(102)));
}
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception {
+ mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged(
+ mUserId, "rule.owner.pkg", "rule_id", AUTOMATIC_RULE_STATUS_ACTIVATED));
+
+ Intent expected = new Intent(ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED)
+ .setPackage("rule.owner.pkg")
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, "rule_id")
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, AUTOMATIC_RULE_STATUS_ACTIVATED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId)));
+ }
+
private static Intent eqIntent(Intent wanted) {
return ArgumentMatchers.argThat(
new ArgumentMatcher<Intent>() {
@Override
public boolean matches(Intent argument) {
return wanted.filterEquals(argument)
- && wanted.getFlags() == argument.getFlags();
+ && wanted.getFlags() == argument.getFlags()
+ && equalBundles(wanted.getExtras(), argument.getExtras());
}
@Override
public String toString() {
return wanted.toString();
}
+
+ private boolean equalBundles(Bundle one, Bundle two) {
+ if (one == null && two == null) {
+ return true;
+ }
+ if ((one == null) != (two == null)) {
+ return false;
+ }
+ if (one.size() != two.size()) {
+ return false;
+ }
+
+ HashSet<String> setOne = new HashSet<>(one.keySet());
+ setOne.addAll(two.keySet());
+
+ for (String key : setOne) {
+ if (!one.containsKey(key) || !two.containsKey(key)) {
+ return false;
+ }
+
+ Object valueOne = one.get(key);
+ Object valueTwo = two.get(key);
+ if (valueOne instanceof Bundle
+ && valueTwo instanceof Bundle
+ && !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+ return false;
+ } else if (valueOne == null) {
+ if (valueTwo != null) {
+ return false;
+ }
+ } else if (!valueOne.equals(valueTwo)) {
+ return false;
+ }
+ }
+ return true;
+ }
});
}
@@ -13310,6 +13373,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
+ enableInteractAcrossUsers();
assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
.containsExactly(new ZenBypassingApp(PKG_P, false));
}
@@ -17307,6 +17371,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -17320,6 +17385,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
@@ -17332,6 +17398,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
@@ -17381,6 +17448,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -17413,6 +17481,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -17427,6 +17496,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -17476,6 +17546,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
.setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
.build();
@@ -17508,6 +17579,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -17536,6 +17608,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
@@ -17563,6 +17636,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ec83e990a70d..194d48a80a65 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -272,7 +272,8 @@ public class VibratorManagerServiceTest {
for (VendorVibrationSession session : mPendingSessions) {
session.cancelSession();
}
- mTestLooper.dispatchAll();
+ // Dispatch and wait for all callbacks in test looper to be processed.
+ stopAutoDispatcherAndDispatchAll();
// Wait until pending vibrations end asynchronously.
for (HalVibration vibration : mPendingVibrations) {
vibration.waitForEnd();
@@ -292,8 +293,6 @@ public class VibratorManagerServiceTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(PowerManagerInternal.class);
LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
- // Ignore potential exceptions about the looper having never dispatched any messages.
- mTestLooper.stopAutoDispatchAndIgnoreExceptions();
if (mInputManagerGlobalSession != null) {
mInputManagerGlobalSession.close();
}
@@ -1240,24 +1239,27 @@ public class VibratorManagerServiceTest {
@EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@Test
- public void vibrate_withOngoingHigherImportanceSession_ignoresEffect() throws Exception {
+ public void vibrate_withOngoingHigherImportanceVendorSession_ignoresEffect() throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
mTestLooper.dispatchAll();
+
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
verify(callback).onStarted(any(IVibrationSession.class));
HalVibration vibration = vibrateAndWaitUntilFinished(service,
VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
HAPTIC_FEEDBACK_ATTRS);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
@@ -1330,24 +1332,28 @@ public class VibratorManagerServiceTest {
@EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@Test
- public void vibrate_withOngoingLowerImportanceSession_cancelsOngoingSession() throws Exception {
+ public void vibrate_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
+ throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
mTestLooper.dispatchAll();
+
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
verify(callback).onStarted(any(IVibrationSession.class));
HalVibration vibration = vibrateAndWaitUntilFinished(service,
VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
HAPTIC_FEEDBACK_ATTRS);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED);
@@ -2393,16 +2399,16 @@ public class VibratorManagerServiceTest {
@EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@Test
- public void onExternalVibration_withOngoingHigherImportanceSession_ignoreNewVibration()
+ public void onExternalVibration_withOngoingHigherImportanceVendorSession_ignoreNewVibration()
throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
mTestLooper.dispatchAll();
verify(callback).onStarted(any(IVibrationSession.class));
@@ -2413,6 +2419,9 @@ public class VibratorManagerServiceTest {
// External vibration is ignored.
assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+
// Session still running.
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
verify(callback, never()).onFinishing();
@@ -2476,16 +2485,16 @@ public class VibratorManagerServiceTest {
@EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@Test
- public void onExternalVibration_withOngoingLowerImportanceSession_cancelsOngoingSession()
+ public void onExternalVibration_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
mTestLooper.dispatchAll();
verify(callback).onStarted(any(IVibrationSession.class));
@@ -2494,7 +2503,9 @@ public class VibratorManagerServiceTest {
ExternalVibrationScale scale =
mExternalVibratorService.onExternalVibrationStart(externalVibration);
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
// Session is cancelled.
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
@@ -2780,9 +2791,17 @@ public class VibratorManagerServiceTest {
assertThrows("Expected starting session without feature flag to fail!",
UnsupportedOperationException.class,
() -> startSession(service, RINGTONE_ATTRS, callback, vibratorId));
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback, never()).onFinished(anyInt());
@@ -2798,10 +2817,18 @@ public class VibratorManagerServiceTest {
IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
callback, vibratorId);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback)
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2817,10 +2844,18 @@ public class VibratorManagerServiceTest {
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
/* callback= */ null, vibratorId);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(session).isNull();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2837,11 +2872,18 @@ public class VibratorManagerServiceTest {
int[] emptyIds = {};
session = startSession(service, RINGTONE_ATTRS, callback, emptyIds);
- assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
- mTestLooper.dispatchAll();
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback, times(2))
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2862,10 +2904,18 @@ public class VibratorManagerServiceTest {
doReturn(token).when(callback).asBinder();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback)
@@ -2882,7 +2932,9 @@ public class VibratorManagerServiceTest {
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2901,6 +2953,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2913,7 +2971,9 @@ public class VibratorManagerServiceTest {
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2925,6 +2985,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2933,12 +2999,12 @@ public class VibratorManagerServiceTest {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
- // Delay not applied when session is aborted.
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2959,12 +3025,12 @@ public class VibratorManagerServiceTest {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
- // Delay not applied when session is aborted.
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2988,6 +3054,7 @@ public class VibratorManagerServiceTest {
throws Exception {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1, 2);
+
VibratorManagerService service = createSystemReadyService();
ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
ArgumentCaptor.forClass(
@@ -2999,7 +3066,9 @@ public class VibratorManagerServiceTest {
IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
doReturn(token).when(callback).asBinder();
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
verify(callback).onStarted(any(IVibrationSession.class));
@@ -3011,6 +3080,11 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -3019,13 +3093,14 @@ public class VibratorManagerServiceTest {
mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
verify(callback).onStarted(captor.capture());
@@ -3051,8 +3126,7 @@ public class VibratorManagerServiceTest {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{10, 10_000}, new int[]{128, 255}, -1);
@@ -3064,7 +3138,9 @@ public class VibratorManagerServiceTest {
service, TEST_TIMEOUT_MILLIS));
VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock, never())
.startSession(eq(session.getSessionId()), any(int[].class));
@@ -3083,8 +3159,7 @@ public class VibratorManagerServiceTest {
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{10, 10_000}, new int[]{128, 255}, -1);
@@ -3098,7 +3173,9 @@ public class VibratorManagerServiceTest {
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
vibration.waitForEnd();
assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS));
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
@@ -3116,8 +3193,7 @@ public class VibratorManagerServiceTest {
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
VibratorManagerService service = createSystemReadyService();
- IVibrationSessionCallback callback =
- mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ IVibrationSessionCallback callback = mockSessionCallbacks();
IBinder firstToken = mock(IBinder.class);
IExternalVibrationController controller = mock(IExternalVibrationController.class);
@@ -3128,7 +3204,9 @@ public class VibratorManagerServiceTest {
mExternalVibratorService.onExternalVibrationStart(externalVibration);
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// The external vibration should have been cancelled
@@ -3149,9 +3227,10 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
int sessionFinishDelayMs = 200;
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3186,9 +3265,10 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
int sessionFinishDelayMs = 200;
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3228,9 +3308,10 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
int sessionFinishDelayMs = 200;
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3272,9 +3353,10 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
int sessionFinishDelayMs = 200;
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3305,6 +3387,10 @@ public class VibratorManagerServiceTest {
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(1));
}
@Test
@@ -3317,9 +3403,10 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
int sessionFinishDelayMs = 200;
IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
- mTestLooper.dispatchAll();
+
+ // Make sure all messages are processed before asserting on the session callbacks.
+ stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3355,6 +3442,10 @@ public class VibratorManagerServiceTest {
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(2));
}
@Test
@@ -3773,6 +3864,10 @@ public class VibratorManagerServiceTest {
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private IVibrationSessionCallback mockSessionCallbacks() {
+ return mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+ }
+
private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) {
Handler handler = new Handler(mTestLooper.getLooper());
ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
@@ -3925,6 +4020,13 @@ public class VibratorManagerServiceTest {
return predicateResult;
}
+ private void stopAutoDispatcherAndDispatchAll() {
+ // Stop auto-dispatcher thread and wait for it to finish processing any messages.
+ mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+ // Dispatch any pending message left.
+ mTestLooper.dispatchAll();
+ }
+
private void grantPermission(String permission) {
when(mContextSpy.checkCallingOrSelfPermission(permission))
.thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index 6dba96766b48..a9a1f93e5ab8 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -76,7 +76,7 @@ public class ConversionUtilTest {
var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(true)
- .setAllowMultipleTriggers(false) // must be false
+ .setMultipleTriggersAllowed(false) // must be false
.setKeyphrases(keyphrases)
.setData(data)
.setAudioCapabilities(flags)
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 1e9038ee1769..32ba8f5f64d1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -468,6 +468,14 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
+ public void testKeyGestureLaunchVoiceAssistant() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT));
+ mPhoneWindowManager.assertSearchManagerLaunchAssist();
+ }
+
+ @Test
public void testKeyGestureGoHome() {
Assert.assertTrue(
sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME));
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index bc03c233b459..9db76d47fed7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -523,12 +523,12 @@ class TestPhoneWindowManager {
}
void prepareBrightnessDecrease(float currentBrightness) {
- doReturn(0.0f).when(mPowerManager)
- .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- doReturn(1.0f).when(mPowerManager)
- .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+ doReturn(0.0f).when(mPowerManager).getBrightnessConstraint(
+ DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+ doReturn(1.0f).when(mPowerManager).getBrightnessConstraint(
+ DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
doReturn(currentBrightness).when(mDisplayManager)
- .getBrightness(0);
+ .getBrightness(DEFAULT_DISPLAY);
}
void verifyNewBrightness(float newBrightness) {
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 fee646d9cb9c..d4a921c5f00a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2670,7 +2670,7 @@ public class ActivityRecordTests extends WindowTestsBase {
assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
// Blanket application, also ignoring Target SDK
- mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+ mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 50c2c2fd926c..a5b2cb39cfff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import android.app.TaskInfo;
+import android.graphics.Rect;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -198,6 +199,34 @@ public class AppCompatUtilsTest extends WindowTestsBase {
});
}
+ @Test
+ public void testTopActivityLetterboxed_hasBounds() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ a.setIgnoreOrientationRequest(true);
+ a.configureTopActivityBounds(new Rect(20, 30, 520, 630));
+ });
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ true);
+
+
+ robot.checkTaskInfoTopActivityHasBounds(/* expected */ new Rect(20, 30, 520, 630));
+ });
+ }
+
+ @Test
+ public void testTopActivityNotLetterboxed_hasNoBounds() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setIgnoreOrientationRequest(true);
+ });
+
+ robot.checkTaskInfoTopActivityHasBounds(/* expected */ null);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -282,6 +311,11 @@ public class AppCompatUtilsTest extends WindowTestsBase {
.cameraCompatTaskInfo.freeformCameraCompatMode);
}
+ void checkTaskInfoTopActivityHasBounds(Rect bounds) {
+ Assert.assertEquals(bounds, getTopTaskInfo().appCompatTaskInfo
+ .topActivityLetterboxBounds);
+ }
+
void setCameraCompatTreatmentEnabledForActivity(boolean enabled) {
doReturn(enabled).when(activity().displayContent().mAppCompatCameraPolicy
.mCameraCompatFreeformPolicy).isTreatmentEnabledForActivity(
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 c427583d3001..3750dd38aa8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -31,6 +31,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -46,22 +49,26 @@ 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;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
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;
import android.content.res.Configuration.Orientation;
import android.graphics.Rect;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -76,6 +83,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.concurrent.Executor;
@@ -137,12 +145,64 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(mDisplayContent,
cameraStateMonitor, mActivityRefresher);
- setDisplayRotation(Surface.ROTATION_90);
+ setDisplayRotation(ROTATION_90);
mCameraCompatFreeformPolicy.start();
cameraStateMonitor.startListeningToCameraState();
}
@Test
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_featureDisabled_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testIsCameraRunningAndWindowingModeEligible_overrideDisabled_returnsFalse() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @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);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @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);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testFullscreen_doesNotActivateCameraCompatMode() {
@@ -176,7 +236,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_0);
+ setDisplayRotation(ROTATION_0);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
@@ -188,7 +248,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
@@ -200,7 +260,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(Surface.ROTATION_0);
+ setDisplayRotation(ROTATION_0);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
@@ -212,7 +272,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
@@ -224,7 +284,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- setDisplayRotation(Surface.ROTATION_270);
+ setDisplayRotation(ROTATION_270);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
@@ -414,6 +474,36 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
/* delta= */ 0.001);
}
+ @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);
+
+ mCameraAvailabilityCallback.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);
+ }
+
+ @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);
+
+ mCameraAvailabilityCallback.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 configureActivity(@ScreenOrientation int activityOrientation) {
configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
}
@@ -444,7 +534,9 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
- doReturn(true).when(mActivity).inFreeformWindowingMode();
+ doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity)
+ .inFreeformWindowingMode();
+ setupMockApplicationThread();
}
private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
@@ -503,9 +595,27 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
// 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;
+ 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);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index ad80f82c8ea8..4810c7fc32d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -22,6 +22,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -137,6 +139,14 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
}
@Test
+ public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() {
+ mCameraStateMonitor.addCameraStateListener(mListener);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mListener.mIsCameraOpened);
+ }
+
+ @Test
public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
mCameraStateMonitor.addCameraStateListener(mListener);
// Listener returns true on `onCameraOpened`.
@@ -144,10 +154,21 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ assertEquals(1, mListener.mCheckCanCloseCounter);
assertEquals(1, mListener.mOnCameraClosedCounter);
}
@Test
+ public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() {
+ mCameraStateMonitor.addCameraStateListener(mListener);
+ // Listener returns true on `onCameraOpened`.
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ assertFalse(mListener.mIsCameraOpened);
+ }
+
+ @Test
public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() {
mCameraStateMonitor.addCameraStateListener(mListenerCannotClose);
// Listener returns true on `onCameraOpened`.
@@ -155,7 +176,8 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- assertEquals(2, mListenerCannotClose.mOnCameraClosedCounter);
+ assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter);
+ assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter);
}
@Test
@@ -197,39 +219,49 @@ public final class CameraStateMonitorTests extends WindowTestsBase {
CameraStateMonitor.CameraCompatStateListener {
int mOnCameraOpenedCounter = 0;
+ int mCheckCanCloseCounter = 0;
int mOnCameraClosedCounter = 0;
- private boolean mOnCameraClosedReturnValue = true;
+ boolean mIsCameraOpened;
+
+ private boolean mCheckCanCloseReturnValue = true;
/**
- * @param simulateUnsuccessfulCloseOnce When false, returns `true` on every
- * `onCameraClosed`. When true, returns `false` on the
- * first `onCameraClosed` callback, and `true on the
+ * @param simulateCannotCloseOnce When false, returns `true` on every
+ * `checkCanClose`. When true, returns `false` on the
+ * first `checkCanClose` callback, and `true on the
* subsequent calls. This fake implementation tests the
* retry mechanism in {@link CameraStateMonitor}.
*/
- FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) {
- mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
+ FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) {
+ mCheckCanCloseReturnValue = !simulateCannotCloseOnce;
}
@Override
- public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
- @NonNull String cameraId) {
+ public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
mOnCameraOpenedCounter++;
+ mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity);
}
@Override
- public boolean onCameraClosed(@NonNull String cameraId) {
- mOnCameraClosedCounter++;
- boolean returnValue = mOnCameraClosedReturnValue;
+ public boolean canCameraBeClosed(@NonNull String cameraId) {
+ mCheckCanCloseCounter++;
+ final boolean returnValue = mCheckCanCloseReturnValue;
// If false, return false only the first time, so it doesn't fall in the infinite retry
// loop.
- mOnCameraClosedReturnValue = true;
+ mCheckCanCloseReturnValue = true;
return returnValue;
}
+ @Override
+ public void onCameraClosed() {
+ mOnCameraClosedCounter++;
+ mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity);
+ }
+
void resetCounters() {
mOnCameraOpenedCounter = 0;
+ mCheckCanCloseCounter = 0;
mOnCameraClosedCounter = 0;
}
}
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 9408f907d058..9cbea2e2f0ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -601,12 +601,12 @@ public class DisplayContentTests extends WindowTestsBase {
TYPE_WALLPAPER, TYPE_APPLICATION);
// Verify not waiting for display without system decorations.
- doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(false).when(secondaryDisplay).isSystemDecorationsSupported();
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify waiting for non-drawn windows on display with system decorations.
reset(secondaryDisplay);
- doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(true).when(secondaryDisplay).isSystemDecorationsSupported();
assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn windows on display with system decorations.
@@ -1865,7 +1865,6 @@ public class DisplayContentTests extends WindowTestsBase {
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -1922,7 +1921,6 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -2263,25 +2261,25 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
- public void testForceDesktopMode() {
+ public void testIsPublicSecondaryDisplayWithDesktopModeForceEnabled() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
- assertFalse(mDefaultDisplay.forceDesktopMode());
+ assertFalse(mDefaultDisplay.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Not applicable for private secondary display.
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.flags = FLAG_PRIVATE;
final DisplayContent privateDc = createNewDisplay(displayInfo);
- assertFalse(privateDc.forceDesktopMode());
+ assertFalse(privateDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Applicable for public secondary display.
final DisplayContent publicDc = createNewDisplay();
- assertTrue(publicDc.forceDesktopMode());
+ assertTrue(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Make sure forceDesktopMode() is false when the force config is disabled.
mWm.mForceDesktopModeOnExternalDisplays = false;
- assertFalse(publicDc.forceDesktopMode());
+ assertFalse(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
index 4557df0e9c98..266ffffabf15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -40,6 +40,9 @@ import org.junit.Test;
@Presubmit
public class DisplayRotationCoordinatorTests {
+ private static final int FIRST_DISPLAY_ID = 1;
+ private static final int SECOND_DISPLAY_ID = 2;
+
@NonNull
private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
@@ -50,22 +53,45 @@ public class DisplayRotationCoordinatorTests {
}
@Test (expected = UnsupportedOperationException.class)
- public void testSecondRegistrationWithoutRemovingFirst() {
+ public void testSecondRegistrationWithoutRemovingFirstWhenDifferentDisplay() {
Runnable callback1 = mock(Runnable.class);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
}
@Test
+ public void testSecondRegistrationWithoutRemovingFirstWhenSameDisplay() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testRemoveIncorrectRegistration() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+
+ // FIRST_DISPLAY_ID is still able to register another callback because the previous
+ // removal should not have succeeded.
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
public void testSecondRegistrationAfterRemovingFirst() {
Runnable callback1 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback1);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
verify(callback2).run();
@@ -75,7 +101,7 @@ public class DisplayRotationCoordinatorTests {
@Test
public void testRegisterThenDefaultDisplayRotationChanged() {
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
verify(callback, never()).run();
@@ -88,7 +114,7 @@ public class DisplayRotationCoordinatorTests {
public void testDefaultDisplayRotationChangedThenRegister() {
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
verify(callback).run();
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index aa992504f9a5..25b9f4b8035b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -717,13 +717,13 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents() {
testVisibleTasks_excludedFromRecents_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_internal();
}
@@ -767,13 +767,13 @@ public class RecentTasksTest extends WindowTestsBase {
@Test
@Ignore("b/342627272")
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() {
testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal();
}
@@ -816,13 +816,13 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+ @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() {
testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal();
}
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 584c4c96ae1d..bf96f0eb03b8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4892,13 +4892,16 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testUniversalResizeable() {
- mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+ mWm.mConstants.mIgnoreActivityOrientationRequestSmallScreen = true;
+ mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
setUpApp(mDisplayContent);
final float maxAspect = 1.8f;
final float minAspect = 1.5f;
prepareLimitedBounds(mActivity, maxAspect, minAspect,
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR, true /* isUnresizable */);
+ assertTrue(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+ mWm, true /* isLargeScreen */, false /* forActivity */));
assertTrue(mActivity.isUniversalResizeable());
assertTrue(mActivity.isResizeable());
assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
@@ -4953,6 +4956,8 @@ public class SizeCompatTests extends WindowTestsBase {
.setComponent(getUniqueComponentName(mContext.getPackageName()))
.setTask(mTask).build();
assertFalse(optOutAppActivity.isUniversalResizeable());
+ assertFalse(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+ mWm, true /* isLargeScreen */, false /* forActivity */));
}
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 b5953839ca8f..f795d93b2487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -605,7 +605,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
@Test
public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
DisplayContent display = createNewDisplay();
- doReturn(true).when(display).supportsSystemDecorations();
+ doReturn(true).when(display).isSystemDecorationsSupported();
// Remove the current home root task if it exists so a new one can be created below.
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
@@ -622,7 +622,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
DisplayContent display = createNewDisplay();
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).supportsSystemDecorations();
+ doReturn(false).when(display).isSystemDecorationsSupported();
assertNull(taskDisplayArea.getRootHomeTask());
assertNull(taskDisplayArea.getOrCreateRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 546b1ba66b08..ccce57a81e41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -204,13 +204,13 @@ class TestDisplayContent extends DisplayContent {
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
if (mSystemDecorations) {
- doReturn(true).when(newDisplay).supportsSystemDecorations();
+ doReturn(true).when(newDisplay).isSystemDecorationsSupported();
doReturn(true).when(displayPolicy).hasNavigationBar();
doReturn(true).when(displayPolicy).hasBottomNavigationBar();
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
- doReturn(false).when(newDisplay).supportsSystemDecorations();
+ doReturn(false).when(newDisplay).isSystemDecorationsSupported();
}
// Update the display policy to make the screen fully turned on so animation is allowed
displayPolicy.screenTurningOn(null /* screenOnListener */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a425401c6238..bfa6cb820a42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -29,6 +29,7 @@ import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -46,6 +47,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -75,6 +77,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -1136,6 +1139,53 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testUpdateInputChannel_sanitizeWithoutPermission_ThrowsError() {
+ final Session session = mock(Session.class);
+ final int callingUid = Process.FIRST_APPLICATION_UID;
+ final int callingPid = 1234;
+ final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final IBinder window = new Binder();
+ final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+
+ final InputChannel inputChannel = new InputChannel();
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
+ surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+ TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
+ "TestInputChannel", inputChannel));
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+ public void testUpdateInputChannel_sanitizeWithPermission_doesNotThrowError() {
+ final Session session = mock(Session.class);
+ final int callingUid = Process.FIRST_APPLICATION_UID;
+ final int callingPid = 1234;
+ final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final IBinder window = new Binder();
+ final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mWm.mContext).checkPermission(
+ android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ callingPid,
+ callingUid);
+
+ final InputChannel inputChannel = new InputChannel();
+
+ mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+ window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */,
+ INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+ TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel",
+ inputChannel);
+ }
+
+ @Test
public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
final Session session = mock(Session.class);
final int callingUid = Process.SYSTEM_UID;
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 757c358f51d0..78e6cbf9c36a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1027,7 +1027,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
@Override
- public void setImeInputTargetRequestedVisibility(boolean visible) {
+ public void setImeInputTargetRequestedVisibility(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
}
};
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 010a32274dcd..e7c9e927b311 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -48,6 +48,7 @@ import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.UidObserver;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.supervision.SupervisionManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
import android.app.usage.BroadcastResponseStatsList;
@@ -230,6 +231,7 @@ public class UsageStatsService extends SystemService implements
// Do not use directly. Call getDpmInternal() instead
DevicePolicyManagerInternal mDpmInternal;
// Do not use directly. Call getShortcutServiceInternal() instead
+ SupervisionManagerInternal mSupervisionManagerInternal;
ShortcutServiceInternal mShortcutServiceInternal;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
@@ -439,6 +441,9 @@ public class UsageStatsService extends SystemService implements
// initialize mDpmInternal
getDpmInternal();
// initialize mShortcutServiceInternal
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ getSupervisionManagerInternal();
+ }
getShortcutServiceInternal();
mResponseStatsTracker.onSystemServicesReady(getContext());
@@ -604,6 +609,15 @@ public class UsageStatsService extends SystemService implements
return mDpmInternal;
}
+ @Nullable
+ private SupervisionManagerInternal getSupervisionManagerInternal() {
+ if (mSupervisionManagerInternal == null) {
+ mSupervisionManagerInternal =
+ LocalServices.getService(SupervisionManagerInternal.class);
+ }
+ return mSupervisionManagerInternal;
+ }
+
private ShortcutServiceInternal getShortcutServiceInternal() {
if (mShortcutServiceInternal == null) {
mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
@@ -753,6 +767,16 @@ public class UsageStatsService extends SystemService implements
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
}
+ private boolean isSupervisionEnabled(int callingUid) {
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ SupervisionManagerInternal smInternal = getSupervisionManagerInternal();
+ return smInternal != null && smInternal.isActiveSupervisionApp(callingUid);
+ } else {
+ DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+ return dpmInternal != null && dpmInternal.isActiveSupervisionApp(callingUid);
+ }
+ }
+
private static void deleteRecursively(final File path) {
if (path.isDirectory()) {
final File[] files = path.listFiles();
@@ -2929,10 +2953,9 @@ public class UsageStatsService extends SystemService implements
long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent,
String callingPackage) {
final int callingUid = Binder.getCallingUid();
- final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && !isSupervisionEnabled(callingUid)) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
@@ -2956,10 +2979,9 @@ public class UsageStatsService extends SystemService implements
@Override
public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
final int callingUid = Binder.getCallingUid();
- final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && !isSupervisionEnabled(callingUid)) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 5ef0fe346dc6..d976f5ba1af2 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -337,7 +337,7 @@ public final class UsbAlsaManager {
deviceAddress, hasOutput, hasInput,
isInputHeadset, isOutputHeadset, isDock);
alsaDevice.setDeviceNameAndDescription(
- cardRec.getCardName(), cardRec.getCardDescription());
+ usbDevice.getProductName(), cardRec.getCardDescription());
if (IS_MULTI_MODE) {
deselectCurrentDevice(alsaDevice.getInputDeviceType());
deselectCurrentDevice(alsaDevice.getOutputDeviceType());
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index fb031bd01673..01ff67400eb1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return;
}
- model.setRequested(config.isAllowMultipleTriggers());
+ model.setRequested(config.isMultipleTriggersAllowed());
// TODO: Remove this block if the lower layer supports multiple triggers.
if (model.isRequested()) {
updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
- modelData.setRequested(config.isAllowMultipleTriggers());
+ modelData.setRequested(config.isMultipleTriggersAllowed());
}
// TODO: Remove this block if the lower layer supports multiple triggers.
if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 19a6ddc3a931..e0cdbddd2efb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1445,7 +1445,7 @@ public class SoundTriggerService extends SystemService {
runOrAddOperation(new Operation(
// always execute:
() -> {
- if (!mRecognitionConfig.isAllowMultipleTriggers()) {
+ if (!mRecognitionConfig.isMultipleTriggersAllowed()) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d89c9c16eb09..7082f0028a5e 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2049,11 +2049,15 @@ public class TelecomManager {
/**
* Ends the foreground call on the device.
* <p>
- * If there is a ringing call, calling this method rejects the ringing call. Otherwise the
+ * If there is a ringing call, calling this method rejects the ringing call. Otherwise, the
* foreground call is ended.
* <p>
* Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false}
* if an attempt is made to end an emergency call.
+ * <p>
+ * Note: If the foreground call on this device is self-managed, this method will only end
+ * the call if the caller of this method is privileged (i.e. system, shell, or root) or system
+ * UI.
*
* @return {@code true} if there is a call which will be rejected or terminated, {@code false}
* otherwise.
@@ -2082,6 +2086,9 @@ public class TelecomManager {
* the incoming call requests. This means, for example, that an incoming call requesting
* {@link VideoProfile#STATE_BIDIRECTIONAL} will be answered, accepting that state.
*
+ * If the ringing incoming call is self-managed, this method will only accept the call if the
+ * caller of this method is privileged (i.e. system, shell, or root) or system UI.
+ *
* @deprecated Companion apps for wearable devices should use the {@link InCallService} API
* instead.
*/
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
index eda0f5b24958..c4a3670e4f14 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
@@ -24,5 +24,5 @@ import com.android.internal.telecom.IInternalServiceRetriever;
* Allows the TelecomLoaderService to pass additional dependencies required for creation.
*/
interface ITelecomLoader {
- ITelecomService createTelecomService(IInternalServiceRetriever retriever);
+ ITelecomService createTelecomService(IInternalServiceRetriever retriever, String sysUiName);
}
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 8fe107cc1ad3..09b18b65be4a 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -109,6 +109,7 @@ public class Annotation {
//TelephonyManager.NETWORK_TYPE_LTE_CA,
TelephonyManager.NETWORK_TYPE_NR,
+ TelephonyManager.NETWORK_TYPE_NB_IOT_NTN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface NetworkType {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7cfdec664a92..e5f1841de641 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9756,9 +9756,8 @@ public class CarrierConfigManager {
* }</pre>
* <p>
* This config is empty by default.
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE =
"regional_satellite_earfcn_bundle";
@@ -9885,15 +9884,14 @@ public class CarrierConfigManager {
"remove_satellite_plmn_in_manual_network_scan_bool";
/**
- * This value is used to set the max datagram size, if the value is not available then the
- * default one will be used.
- * If key is {@code true}, retrieve the max datagram value and use this value always,
- * {@code false} the default value from the modem will be used.
+ * This value is used to set the max datagram size in bytes.
+ * If the value is not available then the default value will be used.
*
- * @hide
+ * The default value is 255 bytes.
*/
- public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE =
- "satellite_sos_max_datagram_size";
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT =
+ "satellite_sos_max_datagram_size_bytes_int";
/** @hide */
@IntDef({
@@ -10061,6 +10059,13 @@ public class CarrierConfigManager {
"satellite_nidd_apn_name_string";
/**
+ * The display name that will be used for satellite functionality within the UI.
+ * The default string value is empty string.
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string";
+
+ /**
* Default value {@code true}, meaning when an emergency call request comes in, if the device is
* in emergency satellite mode but hasn't sent the first satellite datagram, then exits
* satellite mode to allow the emergency call to go through.
@@ -10176,10 +10181,8 @@ public class CarrierConfigManager {
* A string array containing the list of messaging apps that support satellite.
*
* The default value contains only "com.google.android.apps.messaging"
- *
- * @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
"satellite_supported_msg_apps_string_array";
@@ -11343,6 +11346,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
(int) TimeUnit.SECONDS.toMillis(30));
+ sDefaults.putString(KEY_SATELLITE_DISPLAY_NAME_STRING, "");
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
@@ -11450,7 +11454,7 @@ public class CarrierConfigManager {
sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
- sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE, 255);
+ sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT, 255);
}
/**
diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index 90d6f89553b7..8b52f07102b8 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -66,6 +66,9 @@ public class RadioAccessFamily implements Parcelable {
// 5G
public static final int RAF_NR = (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR;
+ /** NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */
+ public static final int RAF_NB_IOT_NTN = (int) TelephonyManager.NETWORK_TYPE_BITMASK_NB_IOT_NTN;
+
// Grouping of RAFs
// 2G
private static final int GSM = RAF_GSM | RAF_GPRS | RAF_EDGE;
@@ -80,6 +83,9 @@ public class RadioAccessFamily implements Parcelable {
// 5G
private static final int NR = RAF_NR;
+ /** Non-Terrestrial Network. */
+ private static final int NB_IOT_NTN = RAF_NB_IOT_NTN;
+
/* Phone ID of phone */
private int mPhoneId;
@@ -258,7 +264,7 @@ public class RadioAccessFamily implements Parcelable {
raf = ((EVDO & raf) > 0) ? (EVDO | raf) : raf;
raf = ((LTE & raf) > 0) ? (LTE | raf) : raf;
raf = ((NR & raf) > 0) ? (NR | raf) : raf;
-
+ raf = ((NB_IOT_NTN & raf) > 0) ? (NB_IOT_NTN | raf) : raf;
return raf;
}
@@ -364,6 +370,7 @@ public class RadioAccessFamily implements Parcelable {
case "WCDMA": return WCDMA;
case "LTE_CA": return RAF_LTE_CA;
case "NR": return RAF_NR;
+ case "NB_IOT_NTN": return RAF_NB_IOT_NTN;
default: return RAF_UNKNOWN;
}
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 127bbff01575..f8c3287fec36 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -233,6 +233,12 @@ public class ServiceState implements Parcelable {
public static final int RIL_RADIO_TECHNOLOGY_NR = 20;
/**
+ * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
+ * @hide
+ */
+ public static final int RIL_RADIO_TECHNOLOGY_NB_IOT_NTN = 21;
+
+ /**
* RIL Radio Annotation
* @hide
*/
@@ -258,14 +264,16 @@ public class ServiceState implements Parcelable {
ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA,
ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA,
- ServiceState.RIL_RADIO_TECHNOLOGY_NR})
+ ServiceState.RIL_RADIO_TECHNOLOGY_NR,
+ ServiceState.RIL_RADIO_TECHNOLOGY_NB_IOT_NTN
+ })
public @interface RilRadioTechnology {}
/**
* The number of the radio technologies.
*/
- private static final int NEXT_RIL_RADIO_TECHNOLOGY = 21;
+ private static final int NEXT_RIL_RADIO_TECHNOLOGY = 22;
/** @hide */
public static final int RIL_RADIO_CDMA_TECHNOLOGY_BITMASK =
@@ -1125,6 +1133,9 @@ public class ServiceState implements Parcelable {
case RIL_RADIO_TECHNOLOGY_NR:
rtString = "NR_SA";
break;
+ case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN:
+ rtString = "NB_IOT_NTN";
+ break;
default:
rtString = "Unexpected";
Rlog.w(LOG_TAG, "Unexpected radioTechnology=" + rt);
@@ -1668,6 +1679,8 @@ public class ServiceState implements Parcelable {
return TelephonyManager.NETWORK_TYPE_LTE_CA;
case RIL_RADIO_TECHNOLOGY_NR:
return TelephonyManager.NETWORK_TYPE_NR;
+ case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN:
+ return TelephonyManager.NETWORK_TYPE_NB_IOT_NTN;
default:
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
@@ -1697,6 +1710,7 @@ public class ServiceState implements Parcelable {
return AccessNetworkType.CDMA2000;
case RIL_RADIO_TECHNOLOGY_LTE:
case RIL_RADIO_TECHNOLOGY_LTE_CA:
+ case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN:
return AccessNetworkType.EUTRAN;
case RIL_RADIO_TECHNOLOGY_NR:
return AccessNetworkType.NGRAN;
@@ -1757,6 +1771,8 @@ public class ServiceState implements Parcelable {
return RIL_RADIO_TECHNOLOGY_LTE_CA;
case TelephonyManager.NETWORK_TYPE_NR:
return RIL_RADIO_TECHNOLOGY_NR;
+ case TelephonyManager.NETWORK_TYPE_NB_IOT_NTN:
+ return RIL_RADIO_TECHNOLOGY_NB_IOT_NTN;
default:
return RIL_RADIO_TECHNOLOGY_UNKNOWN;
}
@@ -1866,7 +1882,8 @@ public class ServiceState implements Parcelable {
|| radioTechnology == RIL_RADIO_TECHNOLOGY_TD_SCDMA
|| radioTechnology == RIL_RADIO_TECHNOLOGY_IWLAN
|| radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
- || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NR
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NB_IOT_NTN;
}
@@ -1886,7 +1903,8 @@ public class ServiceState implements Parcelable {
public static boolean isPsOnlyTech(int radioTechnology) {
return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE
|| radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
- || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NR
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NB_IOT_NTN;
}
/** @hide */
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 377e5f2e7db2..e0af22369182 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3708,7 +3708,7 @@ public class SubscriptionManager {
* Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
* privilege permission of the subscription.
*
- * @param opportunistic whether it’s opportunistic subscription.
+ * @param opportunistic whether it's an opportunistic subscription.
* @param subId the unique SubscriptionInfo index in database
* @return {@code true} if the operation is succeed, {@code false} otherwise.
*
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index e01b10eed4db..bb4ce6e787de 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -16,12 +16,15 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.OverrideNetworkType;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
@@ -94,6 +97,12 @@ public final class TelephonyDisplayInfo implements Parcelable {
private final boolean mIsRoaming;
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsNtn;
+
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsSatelliteConstrainedData;
+
/**
* Constructor
*
@@ -106,7 +115,7 @@ public final class TelephonyDisplayInfo implements Parcelable {
@Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType) {
- this(networkType, overrideNetworkType, false);
+ this(networkType, overrideNetworkType, false, false, false);
}
/**
@@ -118,12 +127,37 @@ public final class TelephonyDisplayInfo implements Parcelable {
*
* @hide
*/
+ @Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType,
boolean isRoaming) {
mNetworkType = networkType;
mOverrideNetworkType = overrideNetworkType;
mIsRoaming = isRoaming;
+ mIsNtn = false;
+ mIsSatelliteConstrainedData = false;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ * @param isRoaming True if the device is roaming after override.
+ * @param isNtn True if the device is camped to non-terrestrial network.
+ * @param isSatelliteConstrainedData True if the device satellite internet is bandwidth
+ * constrained.
+ *
+ * @hide
+ */
+ public TelephonyDisplayInfo(@NetworkType int networkType,
+ @OverrideNetworkType int overrideNetworkType,
+ boolean isRoaming, boolean isNtn, boolean isSatelliteConstrainedData) {
+ mNetworkType = networkType;
+ mOverrideNetworkType = overrideNetworkType;
+ mIsRoaming = isRoaming;
+ mIsNtn = isNtn;
+ mIsSatelliteConstrainedData = isSatelliteConstrainedData;
}
/** @hide */
@@ -131,6 +165,8 @@ public final class TelephonyDisplayInfo implements Parcelable {
mNetworkType = p.readInt();
mOverrideNetworkType = p.readInt();
mIsRoaming = p.readBoolean();
+ mIsNtn = p.readBoolean();
+ mIsSatelliteConstrainedData = p.readBoolean();
}
/**
@@ -170,11 +206,34 @@ public final class TelephonyDisplayInfo implements Parcelable {
return mIsRoaming;
}
+ /**
+ * Get whether the satellite internet is with bandwidth constrained capability set.
+ *
+ * @return {@code true} if satellite internet is connected with bandwidth constrained
+ * capability else {@code false}.
+ * @hide
+ */
+ public boolean isSatelliteConstrainedData() {
+ return mIsSatelliteConstrainedData;
+ }
+
+ /**
+ * Get whether the network is a non-terrestrial network.
+ *
+ * @return {@code true} if network is a non-terrestrial network else {@code false}.
+ * @hide
+ */
+ public boolean isNtn() {
+ return mIsNtn;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mNetworkType);
dest.writeInt(mOverrideNetworkType);
dest.writeBoolean(mIsRoaming);
+ dest.writeBoolean(mIsNtn);
+ dest.writeBoolean(mIsSatelliteConstrainedData);
}
public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -202,12 +261,15 @@ public final class TelephonyDisplayInfo implements Parcelable {
TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
return mNetworkType == that.mNetworkType
&& mOverrideNetworkType == that.mOverrideNetworkType
- && mIsRoaming == that.mIsRoaming;
+ && mIsRoaming == that.mIsRoaming
+ && mIsNtn == that.mIsNtn
+ && mIsSatelliteConstrainedData == that.mIsSatelliteConstrainedData;
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
+ return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming, mIsNtn,
+ mIsSatelliteConstrainedData);
}
/**
@@ -233,6 +295,8 @@ public final class TelephonyDisplayInfo implements Parcelable {
public String toString() {
return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+ ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
- + ", isRoaming=" + mIsRoaming + "}";
+ + ", isRoaming=" + mIsRoaming
+ + ", isNtn=" + mIsNtn
+ + ", isSatelliteConstrainedData=" + mIsSatelliteConstrainedData + "}";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ad5d42ad5a68..aec11c45008a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3114,6 +3114,12 @@ public class TelephonyManager {
* For 5G NSA, the network type will be {@link #NETWORK_TYPE_LTE}.
*/
public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20.
+ /**
+ * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ public static final int NETWORK_TYPE_NB_IOT_NTN =
+ TelephonyProtoEnums.NETWORK_TYPE_NB_IOT_NTN; // 21
private static final @NetworkType int[] NETWORK_TYPES = {
NETWORK_TYPE_GPRS,
@@ -3190,6 +3196,7 @@ public class TelephonyManager {
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
* @see #NETWORK_TYPE_NR
+ * @see #NETWORK_TYPE_NB_IOT_NTN
*
* @hide
*/
@@ -3250,6 +3257,7 @@ public class TelephonyManager {
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
* @see #NETWORK_TYPE_NR
+ * @see #NETWORK_TYPE_NB_IOT_NTN
*
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
@@ -3400,6 +3408,8 @@ public class TelephonyManager {
return "LTE_CA";
case NETWORK_TYPE_NR:
return "NR";
+ case NETWORK_TYPE_NB_IOT_NTN:
+ return "NB_IOT_NTN";
case NETWORK_TYPE_UNKNOWN:
return "UNKNOWN";
default:
@@ -3450,6 +3460,8 @@ public class TelephonyManager {
return NETWORK_TYPE_BITMASK_LTE;
case NETWORK_TYPE_NR:
return NETWORK_TYPE_BITMASK_NR;
+ case NETWORK_TYPE_NB_IOT_NTN:
+ return NETWORK_TYPE_BITMASK_NB_IOT_NTN;
case NETWORK_TYPE_IWLAN:
return NETWORK_TYPE_BITMASK_IWLAN;
case NETWORK_TYPE_IDEN:
@@ -5251,6 +5263,36 @@ public class TelephonyManager {
}
/**
+ * Returns the Group Identifier Level 2 in hexadecimal format.
+ * @return the Group Identifier Level 2 for the SIM card.
+ * Return null if it is unavailable.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_GET_GROUP_ID_LEVEL2)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @SystemApi
+ @Nullable
+ public String getGroupIdLevel2() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ return null;
+ }
+ return info.getGroupIdLevel2ForSubscriber(getSubId(), mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return null;
+ }
+ }
+
+ /**
* Returns the phone number string for line 1, for example, the MSISDN
* for a GSM phone for a particular subscription. Return null if it is unavailable.
* <p>
@@ -10130,6 +10172,9 @@ public class TelephonyManager {
* This API will result in allowing an intersection of allowed network types for all reasons,
* including the configuration done through other reasons.
*
+ * If device supports satellite service, then
+ * {@link #NETWORK_TYPE_NB_IOT_NTN} is added to allowed network types for reason by default.
+ *
* @param reason the reason the allowed network type change is taking place
* @param allowedNetworkTypes The bitmask of allowed network type
* @throws IllegalStateException if the Telephony process is not currently available.
@@ -10179,6 +10224,10 @@ public class TelephonyManager {
* <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE or
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
+ * If device supports satellite service, then
+ * {@link #NETWORK_TYPE_NB_IOT_NTN} is added to allowed network types for reason by
+ * default.
+ *
* @param reason the reason the allowed network type change is taking place
* @return the allowed network type bitmask
* @throws IllegalStateException if the Telephony process is not currently available.
@@ -10245,7 +10294,7 @@ public class TelephonyManager {
*/
public static String convertNetworkTypeBitmaskToString(
@NetworkTypeBitMask long networkTypeBitmask) {
- String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NR)
+ String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NB_IOT_NTN)
.filter(x -> {
return (networkTypeBitmask & getBitMaskForNetworkType(x))
== getBitMaskForNetworkType(x);
@@ -14875,7 +14924,8 @@ public class TelephonyManager {
NETWORK_TYPE_BITMASK_LTE_CA,
NETWORK_TYPE_BITMASK_NR,
NETWORK_TYPE_BITMASK_IWLAN,
- NETWORK_TYPE_BITMASK_IDEN
+ NETWORK_TYPE_BITMASK_IDEN,
+ NETWORK_TYPE_BITMASK_NB_IOT_NTN
})
public @interface NetworkTypeBitMask {}
@@ -14976,6 +15026,12 @@ public class TelephonyManager {
*/
public static final long NETWORK_TYPE_BITMASK_IWLAN = (1 << (NETWORK_TYPE_IWLAN -1));
+ /**
+ * network type bitmask indicating the support of readio tech NB IOT NTN.
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ public static final long NETWORK_TYPE_BITMASK_NB_IOT_NTN = (1 << (NETWORK_TYPE_NB_IOT_NTN - 1));
+
/** @hide */
public static final long NETWORK_CLASS_BITMASK_2G = NETWORK_TYPE_BITMASK_GSM
| NETWORK_TYPE_BITMASK_GPRS
@@ -15004,6 +15060,9 @@ public class TelephonyManager {
public static final long NETWORK_CLASS_BITMASK_5G = NETWORK_TYPE_BITMASK_NR;
/** @hide */
+ public static final long NETWORK_CLASS_BITMASK_NTN = NETWORK_TYPE_BITMASK_NB_IOT_NTN;
+
+ /** @hide */
public static final long NETWORK_STANDARDS_FAMILY_BITMASK_3GPP = NETWORK_TYPE_BITMASK_GSM
| NETWORK_TYPE_BITMASK_GPRS
| NETWORK_TYPE_BITMASK_EDGE
@@ -15015,7 +15074,8 @@ public class TelephonyManager {
| NETWORK_TYPE_BITMASK_TD_SCDMA
| NETWORK_TYPE_BITMASK_LTE
| NETWORK_TYPE_BITMASK_LTE_CA
- | NETWORK_TYPE_BITMASK_NR;
+ | NETWORK_TYPE_BITMASK_NR
+ | NETWORK_TYPE_BITMASK_NB_IOT_NTN;
/** @hide */
public static final long NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2 = NETWORK_TYPE_BITMASK_CDMA
@@ -18053,7 +18113,7 @@ public class TelephonyManager {
*/
public static boolean isNetworkTypeValid(@NetworkType int networkType) {
return networkType >= TelephonyManager.NETWORK_TYPE_UNKNOWN &&
- networkType <= TelephonyManager.NETWORK_TYPE_NR;
+ networkType <= TelephonyManager.NETWORK_TYPE_NB_IOT_NTN;
}
/**
@@ -19606,7 +19666,7 @@ public class TelephonyManager {
* Android assigns each carrier with a canonical integer a.k.a. carrier id.
* The carrier ID is an Android platform-wide identifier for a carrier.
* AOSP maintains carrier ID assignments in
- * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
+ * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/main/assets/latest_carrier_id/carrier_list.textpb">here</a>
*
* @param carrierIdentifier {@link CarrierIdentifier}
*
diff --git a/telephony/java/android/telephony/satellite/EarfcnRange.java b/telephony/java/android/telephony/satellite/EarfcnRange.java
index 207b25d60d90..81c8ed326b64 100644
--- a/telephony/java/android/telephony/satellite/EarfcnRange.java
+++ b/telephony/java/android/telephony/satellite/EarfcnRange.java
@@ -19,6 +19,7 @@ package android.telephony.satellite;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,7 +41,8 @@ import java.util.Objects;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class EarfcnRange implements Parcelable {
/**
@@ -74,6 +76,7 @@ public final class EarfcnRange implements Parcelable {
*
* @param startEarfcn The starting earfcn value.
* @param endEarfcn The ending earfcn value.
+ * @hide
*/
public EarfcnRange(@IntRange(from = 0, to = 65535) int startEarfcn,
@IntRange(from = 0, to = 65535) int endEarfcn) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
index c1a6ae850985..56c92d04410b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
+++ b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,7 +35,8 @@ import java.util.Objects;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class SatelliteAccessConfiguration implements Parcelable {
/**
* The list of satellites available at the current location.
@@ -54,6 +56,7 @@ public final class SatelliteAccessConfiguration implements Parcelable {
* @param satelliteInfos The list of {@link SatelliteInfo} objects representing the satellites
* accessible with this configuration.
* @param tagIdList The list of tag IDs associated with this configuration.
+ * @hide
*/
public SatelliteAccessConfiguration(@NonNull List<SatelliteInfo> satelliteInfos,
@NonNull List<Integer> tagIdList) {
@@ -61,12 +64,18 @@ public final class SatelliteAccessConfiguration implements Parcelable {
mTagIdList = tagIdList;
}
+ /**
+ * Constructor for {@link SatelliteAccessConfiguration}.
+ * @param in parcel used to create {@link SatelliteAccessConfiguration} object
+ * @hide
+ */
public SatelliteAccessConfiguration(Parcel in) {
mSatelliteInfoList = in.createTypedArrayList(SatelliteInfo.CREATOR);
mTagIdList = new ArrayList<>();
in.readList(mTagIdList, Integer.class.getClassLoader(), Integer.class);
}
+ @NonNull
public static final Creator<SatelliteAccessConfiguration> CREATOR =
new Creator<SatelliteAccessConfiguration>() {
@Override
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
index bffb11f23d56..6291102cd6e3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -18,6 +18,8 @@ package android.telephony.satellite;
import android.annotation.FlaggedApi;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
import com.android.internal.telephony.flags.Flags;
@@ -27,19 +29,19 @@ import com.android.internal.telephony.flags.Flags;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public interface SatelliteCommunicationAllowedStateCallback {
/**
* Telephony does not guarantee that whenever there is a change in communication allowed state,
* this API will be called. Telephony does its best to detect the changes and notify its
- * listeners accordingly.
+ * listeners accordingly. Satellite communication is allowed at a location when it is legally
+ * allowed by the local authority and satellite signal coverage is available.
*
- * @param isAllowed {@code true} means satellite allow state is changed,
- * {@code false} satellite allow state is not changed
- * @hide
+ * @param isAllowed {@code true} means satellite is allowed,
+ * {@code false} satellite is not allowed.
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
/**
@@ -49,9 +51,7 @@ public interface SatelliteCommunicationAllowedStateCallback {
* the current location. When satellite is not allowed at
* the current location,
* {@code satelliteRegionalConfiguration} will be null.
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
default void onSatelliteAccessConfigurationChanged(
@Nullable SatelliteAccessConfiguration satelliteAccessConfiguration) {};
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
index 5e276aa49b05..0a1eedf097a4 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -18,6 +18,7 @@ package android.telephony.satellite;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import com.android.internal.telephony.flags.Flags;
@@ -26,13 +27,14 @@ import com.android.internal.telephony.flags.Flags;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public interface SatelliteDisallowedReasonsCallback {
/**
* Called when disallowed reason of satellite has changed.
* @param disallowedReasons Integer array of disallowed reasons.
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+ void onSatelliteDisallowedReasonsChanged(
+ @NonNull @SatelliteManager.SatelliteDisallowedReason int[] disallowedReasons);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteInfo.java b/telephony/java/android/telephony/satellite/SatelliteInfo.java
index 1d5f613cc086..ebb4e9c99549 100644
--- a/telephony/java/android/telephony/satellite/SatelliteInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteInfo.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -36,8 +37,9 @@ import java.util.UUID;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatelliteInfo implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatelliteInfo implements Parcelable {
/**
* Unique identification number for the satellite.
* This ID is used to distinguish between different satellites in the network.
@@ -68,6 +70,9 @@ public class SatelliteInfo implements Parcelable {
*/
private final List<EarfcnRange> mEarfcnRangeList;
+ /**
+ * @hide
+ */
protected SatelliteInfo(Parcel in) {
ParcelUuid parcelUuid = in.readParcelable(
ParcelUuid.class.getClassLoader(), ParcelUuid.class);
@@ -89,6 +94,7 @@ public class SatelliteInfo implements Parcelable {
* @param bandList The list of frequency bandList supported by the satellite.
* @param earfcnRanges The list of {@link EarfcnRange} objects representing the EARFCN
* ranges supported by the satellite.
+ * @hide
*/
public SatelliteInfo(@NonNull UUID satelliteId, @NonNull SatellitePosition satellitePosition,
@NonNull List<Integer> bandList, @NonNull List<EarfcnRange> earfcnRanges) {
@@ -98,6 +104,7 @@ public class SatelliteInfo implements Parcelable {
mEarfcnRangeList = earfcnRanges;
}
+ @NonNull
public static final Creator<SatelliteInfo> CREATOR = new Creator<SatelliteInfo>() {
@Override
public SatelliteInfo createFromParcel(Parcel in) {
@@ -135,6 +142,10 @@ public class SatelliteInfo implements Parcelable {
/**
* Returns the position of the satellite.
+ * Position information of a geostationary satellite.
+ * This includes the longitude and altitude of the satellite.
+ * If the SatellitePosition is invalid,
+ * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
*
* @return The {@link SatellitePosition} of the satellite.
*/
@@ -146,6 +157,8 @@ public class SatelliteInfo implements Parcelable {
/**
* Returns the list of frequency bands supported by the satellite.
*
+ * Refer specification 3GPP TS 36.101 for detailed information on frequency bands.
+ *
* @return The list of frequency bands.
*/
@NonNull
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 5a34b0038872..0f23f337d851 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -221,6 +221,14 @@ public final class SatelliteManager {
/**
* Bundle key to get the response from
+ * {@link #requestSessionStats(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+
+ public static final String KEY_SESSION_STATS_V2 = "session_stats_v2";
+
+ /**
+ * Bundle key to get the response from
* {@link #requestIsProvisioned(Executor, OutcomeReceiver)}.
* @hide
*/
@@ -284,6 +292,13 @@ public final class SatelliteManager {
/**
* Bundle key to get the response from
+ * {@link #requestSatelliteDisplayName(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_DISPLAY_NAME = "satellite_display_name";
+
+ /**
+ * Bundle key to get the response from
* {@link #requestSelectedNbIotSatelliteSubscriptionId(Executor, OutcomeReceiver)}.
* @hide
*/
@@ -545,6 +560,8 @@ public final class SatelliteManager {
* There is no valid satellite subscription selected.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30;
/** @hide */
@@ -747,6 +764,8 @@ public final class SatelliteManager {
* config_satellite_gateway_service_package
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED =
"android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
@@ -758,6 +777,8 @@ public final class SatelliteManager {
* config_satellite_gateway_service_package
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION =
"android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
/**
@@ -773,6 +794,8 @@ public final class SatelliteManager {
* }
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT =
"android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
@@ -2372,8 +2395,9 @@ public final class SatelliteManager {
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void requestSatelliteAccessConfigurationForCurrentLocation(
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<SatelliteAccessConfiguration, SatelliteException> callback) {
@@ -2486,7 +2510,7 @@ public final class SatelliteManager {
* @param executor The executor on which the callback will be called.
* @param callback The callback object to which the result will be delivered.
* If the request is successful, {@link OutcomeReceiver#onResult(Object)}
- * will return the time after which the satellite will be visible.
+ * will return the selected NB IOT satellite subscription ID.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
@@ -2494,6 +2518,8 @@ public final class SatelliteManager {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void requestSelectedNbIotSatelliteSubscriptionId(
@NonNull @CallbackExecutor Executor executor,
@@ -2553,6 +2579,8 @@ public final class SatelliteManager {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteResult public int registerForSelectedNbIotSatelliteSubscriptionChanged(
@NonNull @CallbackExecutor Executor executor,
@@ -2598,6 +2626,8 @@ public final class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
@NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
@@ -2876,27 +2906,22 @@ public final class SatelliteManager {
/**
* Returns list of disallowed reasons of satellite.
*
- * @return list of disallowed reasons of satellite.
+ * @return Integer array of disallowed reasons.
*
* @throws SecurityException if caller doesn't have required permission.
* @throws IllegalStateException if Telephony process isn't available.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteDisallowedReason
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@NonNull
- public List<Integer> getSatelliteDisallowedReasons() {
+ public int[] getSatelliteDisallowedReasons() {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- int[] receivedArray = telephony.getSatelliteDisallowedReasons();
- if (receivedArray.length == 0) {
- logd("receivedArray is empty, create empty list");
- return new ArrayList<>();
- } else {
- return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
- }
+ return telephony.getSatelliteDisallowedReasons();
} else {
throw new IllegalStateException("Telephony service is null.");
}
@@ -2904,7 +2929,7 @@ public final class SatelliteManager {
loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
ex.rethrowAsRuntimeException();
}
- return new ArrayList<>();
+ return new int[0];
}
/**
@@ -2917,8 +2942,9 @@ public final class SatelliteManager {
* @throws IllegalStateException if Telephony process is not available.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void registerForSatelliteDisallowedReasonsChanged(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteDisallowedReasonsCallback callback) {
@@ -2960,8 +2986,9 @@ public final class SatelliteManager {
* @throws IllegalStateException if Telephony process is not available.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void unregisterForSatelliteDisallowedReasonsChanged(
@NonNull SatelliteDisallowedReasonsCallback callback) {
Objects.requireNonNull(callback);
@@ -3274,13 +3301,14 @@ public final class SatelliteManager {
* @param executor The executor on which the callback will be called.
* @param callback The callback to handle the satellite supoprted state changed event.
*
- * @return The {@link SatelliteResult} result of the operation.
+ * @return The result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- *
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteResult public int registerForSupportedStateChanged(
@NonNull @CallbackExecutor Executor executor,
@@ -3322,11 +3350,11 @@ public final class SatelliteManager {
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- *
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void unregisterForSupportedStateChanged(
@NonNull SatelliteSupportedStateCallback callback) {
Objects.requireNonNull(callback);
@@ -3355,11 +3383,13 @@ public final class SatelliteManager {
*
* @param executor The executor on which the callback will be called.
* @param callback The callback to handle satellite communication allowed state changed event.
- * @return The {@link SatelliteResult} result of the operation.
+ * @return The result of the operation.
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@SatelliteResult
public int registerForCommunicationAllowedStateChanged(
@@ -3414,8 +3444,9 @@ public final class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void unregisterForCommunicationAllowedStateChanged(
@NonNull SatelliteCommunicationAllowedStateCallback callback) {
Objects.requireNonNull(callback);
@@ -3455,7 +3486,6 @@ public final class SatelliteManager {
*/
@RequiresPermission(allOf = {Manifest.permission.PACKAGE_USAGE_STATS,
Manifest.permission.MODIFY_PHONE_STATE})
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public void requestSessionStats(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<SatelliteSessionStats, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -3468,21 +3498,33 @@ public final class SatelliteManager {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ SatelliteSessionStats stats;
if (resultData.containsKey(KEY_SESSION_STATS)) {
- SatelliteSessionStats stats =
- resultData.getParcelable(KEY_SESSION_STATS,
- SatelliteSessionStats.class);
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onResult(stats)));
+ stats = resultData.getParcelable(KEY_SESSION_STATS,
+ SatelliteSessionStats.class);
+ if (resultData.containsKey(KEY_SESSION_STATS_V2)) {
+ SatelliteSessionStats stats1 = resultData.getParcelable(
+ KEY_SESSION_STATS_V2, SatelliteSessionStats.class);
+ if (stats != null && stats1 != null) {
+ stats.setSatelliteSessionStats(
+ stats1.getSatelliteSessionStats());
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onResult(stats)));
+ return;
+ }
+ } else {
+ loge("KEY_SESSION_STATS_V2 does not exist.");
+ }
} else {
loge("KEY_SESSION_STATS does not exist.");
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(new SatelliteException(
- SATELLITE_RESULT_REQUEST_FAILED))));
}
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+
} else {
- executor.execute(() -> Binder.withCleanCallingIdentity(() ->
- callback.onError(new SatelliteException(resultCode))));
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onError(new SatelliteException(resultCode))));
}
}
};
@@ -3515,8 +3557,9 @@ public final class SatelliteManager {
* @throws SecurityException if the caller doesn't have required permission.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void requestSatelliteSubscriberProvisionStatus(
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<List<SatelliteSubscriberProvisionStatus>,
@@ -3564,6 +3607,67 @@ public final class SatelliteManager {
}
/**
+ * Request to get the display name of satellite feature in the UI.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return display name of the satellite feature in string format. Default
+ * display name is "Satellite". If the request is not successful,
+ * {@link OutcomeReceiver#onError(Throwable)} will return an error with
+ * a SatelliteException.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void requestSatelliteDisplayName(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<CharSequence, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_DISPLAY_NAME)) {
+ CharSequence satelliteDisplayName =
+ resultData.getString(KEY_SATELLITE_DISPLAY_NAME);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(satelliteDisplayName)));
+ } else {
+ loge("KEY_SATELLITE_DISPLAY_NAME does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestSatelliteDisplayName(receiver);
+ } else {
+ loge("requestSatelliteDisplayName() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestSatelliteDisplayName() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
+ /**
* Deliver the list of provisioned satellite subscriber infos.
*
* @param list The list of provisioned satellite subscriber infos.
@@ -3573,8 +3677,9 @@ public final class SatelliteManager {
* @throws SecurityException if the caller doesn't have required permission.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -3628,8 +3733,9 @@ public final class SatelliteManager {
* @throws SecurityException if the caller doesn't have required permission.
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
index 5e56f84ab1a2..d7fa3c9265e0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
@@ -16,10 +16,14 @@
package android.telephony.satellite;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
@@ -28,6 +32,8 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class SatelliteModemEnableRequestAttributes implements Parcelable {
/** {@code true} to enable satellite and {@code false} to disable satellite */
@@ -47,6 +53,9 @@ public final class SatelliteModemEnableRequestAttributes implements Parcelable {
/** The subscription related info */
@NonNull private final SatelliteSubscriptionInfo mSatelliteSubscriptionInfo;
+ /**
+ * @hide
+ */
public SatelliteModemEnableRequestAttributes(boolean isEnabled, boolean isDemoMode,
boolean isEmergencyMode, @NonNull SatelliteSubscriptionInfo satelliteSubscriptionInfo) {
mIsEnabled = isEnabled;
@@ -76,6 +85,7 @@ public final class SatelliteModemEnableRequestAttributes implements Parcelable {
mSatelliteSubscriptionInfo.writeToParcel(dest, flags);
}
+ @NonNull
public static final Creator<SatelliteModemEnableRequestAttributes> CREATOR = new Creator<>() {
@Override
public SatelliteModemEnableRequestAttributes createFromParcel(Parcel in) {
@@ -114,19 +124,39 @@ public final class SatelliteModemEnableRequestAttributes implements Parcelable {
return Objects.hash(mIsEnabled, mIsDemoMode, mIsEmergencyMode, mSatelliteSubscriptionInfo);
}
+
+ /**
+ * Get whether satellite modem needs to be enabled or disabled.
+ * @return {@code true} if the request is to enable satellite, else {@code false} to disable
+ * satellite.
+ */
public boolean isEnabled() {
return mIsEnabled;
}
+ /**
+ * Get whether satellite modem is enabled for demo mode.
+ * @return {@code true} if the request is to enable demo mode, else {@code false}.
+ */
public boolean isDemoMode() {
return mIsDemoMode;
}
+ /**
+ * Get whether satellite modem is enabled for emergency mode.
+ * @return {@code true} if the request is to enable satellite for emergency mode,
+ * else {@code false}.
+ */
public boolean isEmergencyMode() {
return mIsEmergencyMode;
}
- @NonNull public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
+
+ /**
+ * Return subscription info related to satellite.
+ */
+ @NonNull
+ public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
return mSatelliteSubscriptionInfo;
}
}
diff --git a/telephony/java/android/telephony/satellite/SatellitePosition.java b/telephony/java/android/telephony/satellite/SatellitePosition.java
index dd463e00ebb5..b8d138fe5c17 100644
--- a/telephony/java/android/telephony/satellite/SatellitePosition.java
+++ b/telephony/java/android/telephony/satellite/SatellitePosition.java
@@ -16,6 +16,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,8 +35,9 @@ import java.util.Objects;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatellitePosition implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatellitePosition implements Parcelable {
/**
* The longitude of the satellite in degrees, ranging from -180 to 180 degrees
@@ -51,6 +53,7 @@ public class SatellitePosition implements Parcelable {
* Constructor for {@link SatellitePosition} used to create an instance from a {@link Parcel}.
*
* @param in The {@link Parcel} to read the satellite position data from.
+ * @hide
*/
public SatellitePosition(Parcel in) {
mLongitudeDegree = in.readDouble();
@@ -62,12 +65,14 @@ public class SatellitePosition implements Parcelable {
*
* @param longitudeDegree The longitude of the satellite in degrees.
* @param altitudeKm The altitude of the satellite in kilometers.
+ * @hide
*/
public SatellitePosition(double longitudeDegree, double altitudeKm) {
mLongitudeDegree = longitudeDegree;
mAltitudeKm = altitudeKm;
}
+ @NonNull
public static final Creator<SatellitePosition> CREATOR = new Creator<SatellitePosition>() {
@Override
public SatellitePosition createFromParcel(Parcel in) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index e8ae0f56ee9e..6b95eb3c1ac3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -48,9 +48,8 @@ public interface SatelliteProvisionStateCallback {
*
* @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states
* of the SatelliteSubscriberInfos.
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
default void onSatelliteSubscriptionProvisionStateChanged(
@NonNull List<SatelliteSubscriberProvisionStatus>
satelliteSubscriberProvisionStatus) {};
diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
index aabb058691d8..0cdba83415c2 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
@@ -19,23 +19,37 @@ package android.telephony.satellite;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
/**
* SatelliteSessionStats is used to represent the usage stats of the satellite service.
+ *
* @hide
*/
-public class SatelliteSessionStats implements Parcelable {
+public final class SatelliteSessionStats implements Parcelable {
private int mCountOfSuccessfulUserMessages;
private int mCountOfUnsuccessfulUserMessages;
private int mCountOfTimedOutUserMessagesWaitingForConnection;
private int mCountOfTimedOutUserMessagesWaitingForAck;
private int mCountOfUserMessagesInQueueToBeSent;
+ private long mLatencyOfSuccessfulUserMessages;
+
+ private Map<Integer, SatelliteSessionStats> datagramStats;
+ private long mMaxLatency;
+ private long mLastMessageLatency;
+
+ public SatelliteSessionStats() {
+ this.datagramStats = new HashMap<>();
+ }
/**
* SatelliteSessionStats constructor
- * @param builder Builder to create SatelliteSessionStats object/
+ *
+ * @param builder Builder to create SatelliteSessionStats object/
*/
public SatelliteSessionStats(@NonNull Builder builder) {
mCountOfSuccessfulUserMessages = builder.mCountOfSuccessfulUserMessages;
@@ -45,6 +59,7 @@ public class SatelliteSessionStats implements Parcelable {
mCountOfTimedOutUserMessagesWaitingForAck =
builder.mCountOfTimedOutUserMessagesWaitingForAck;
mCountOfUserMessagesInQueueToBeSent = builder.mCountOfUserMessagesInQueueToBeSent;
+ mLatencyOfSuccessfulUserMessages = builder.mLatencyOfSuccessfulUserMessages;
}
private SatelliteSessionStats(Parcel in) {
@@ -63,6 +78,19 @@ public class SatelliteSessionStats implements Parcelable {
out.writeInt(mCountOfTimedOutUserMessagesWaitingForConnection);
out.writeInt(mCountOfTimedOutUserMessagesWaitingForAck);
out.writeInt(mCountOfUserMessagesInQueueToBeSent);
+ out.writeLong(mLatencyOfSuccessfulUserMessages);
+ out.writeLong(mMaxLatency);
+ out.writeLong(mLastMessageLatency);
+
+ if (datagramStats != null && !datagramStats.isEmpty()) {
+ out.writeInt(datagramStats.size());
+ for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) {
+ out.writeInt(entry.getKey());
+ out.writeParcelable(entry.getValue(), flags);
+ }
+ } else {
+ out.writeInt(0);
+ }
}
@NonNull
@@ -83,6 +111,40 @@ public class SatelliteSessionStats implements Parcelable {
@NonNull
public String toString() {
StringBuilder sb = new StringBuilder();
+ if (datagramStats != null) {
+ sb.append(" ====== SatelliteSessionStatsWrapper Info =============");
+ for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) {
+ Integer key = entry.getKey();
+ SatelliteSessionStats value = entry.getValue();
+ sb.append("\n");
+ sb.append("Key:");
+ sb.append(key);
+ sb.append(", SatelliteSessionStats:[");
+ value.getPrintableCounters(sb);
+ sb.append(",");
+ sb.append(" LatencyOfSuccessfulUserMessages:");
+ sb.append(value.mLatencyOfSuccessfulUserMessages);
+ sb.append(",");
+ sb.append(" mMaxLatency:");
+ sb.append(value.mMaxLatency);
+ sb.append(",");
+ sb.append(" mLastMessageLatency:");
+ sb.append(value.mLastMessageLatency);
+ sb.append("]");
+ sb.append("\n");
+ }
+ sb.append(" ============== ================== ===============");
+ sb.append("\n");
+ sb.append("\n");
+ } else {
+ sb.append("\n");
+ getPrintableCounters(sb);
+ }
+ sb.append("\n");
+ return sb.toString();
+ }
+
+ private void getPrintableCounters(StringBuilder sb) {
sb.append("countOfSuccessfulUserMessages:");
sb.append(mCountOfSuccessfulUserMessages);
sb.append(",");
@@ -101,7 +163,6 @@ public class SatelliteSessionStats implements Parcelable {
sb.append("countOfUserMessagesInQueueToBeSent:");
sb.append(mCountOfUserMessagesInQueueToBeSent);
- return sb.toString();
}
@Override
@@ -110,49 +171,176 @@ public class SatelliteSessionStats implements Parcelable {
if (o == null || getClass() != o.getClass()) return false;
SatelliteSessionStats that = (SatelliteSessionStats) o;
return mCountOfSuccessfulUserMessages == that.mCountOfSuccessfulUserMessages
+ && mLatencyOfSuccessfulUserMessages == that.mLatencyOfSuccessfulUserMessages
&& mCountOfUnsuccessfulUserMessages == that.mCountOfUnsuccessfulUserMessages
&& mCountOfTimedOutUserMessagesWaitingForConnection
== that.mCountOfTimedOutUserMessagesWaitingForConnection
&& mCountOfTimedOutUserMessagesWaitingForAck
== that.mCountOfTimedOutUserMessagesWaitingForAck
- && mCountOfUserMessagesInQueueToBeSent
- == that.mCountOfUserMessagesInQueueToBeSent;
+ && mCountOfUserMessagesInQueueToBeSent == that.mCountOfUserMessagesInQueueToBeSent;
}
@Override
public int hashCode() {
- return Objects.hash(mCountOfSuccessfulUserMessages, mCountOfUnsuccessfulUserMessages,
- mCountOfTimedOutUserMessagesWaitingForConnection,
- mCountOfTimedOutUserMessagesWaitingForAck,
- mCountOfUserMessagesInQueueToBeSent);
+ return Objects.hash(mCountOfSuccessfulUserMessages, mLatencyOfSuccessfulUserMessages,
+ mCountOfUnsuccessfulUserMessages, mCountOfTimedOutUserMessagesWaitingForConnection,
+ mCountOfTimedOutUserMessagesWaitingForAck, mCountOfUserMessagesInQueueToBeSent);
}
public int getCountOfSuccessfulUserMessages() {
return mCountOfSuccessfulUserMessages;
}
+ public void incrementSuccessfulUserMessageCount() {
+ mCountOfSuccessfulUserMessages++;
+ }
+
public int getCountOfUnsuccessfulUserMessages() {
return mCountOfUnsuccessfulUserMessages;
}
+ public void incrementUnsuccessfulUserMessageCount() {
+ mCountOfUnsuccessfulUserMessages++;
+ }
+
public int getCountOfTimedOutUserMessagesWaitingForConnection() {
return mCountOfTimedOutUserMessagesWaitingForConnection;
}
+ public void incrementTimedOutUserMessagesWaitingForConnection() {
+ mCountOfTimedOutUserMessagesWaitingForConnection++;
+ }
+
public int getCountOfTimedOutUserMessagesWaitingForAck() {
return mCountOfTimedOutUserMessagesWaitingForAck;
}
+ public void incrementTimedOutUserMessagesWaitingForAck() {
+ mCountOfTimedOutUserMessagesWaitingForAck++;
+ }
+
public int getCountOfUserMessagesInQueueToBeSent() {
return mCountOfUserMessagesInQueueToBeSent;
}
+ public long getLatencyOfAllSuccessfulUserMessages() {
+ return mLatencyOfSuccessfulUserMessages;
+ }
+
+ public void updateLatencyOfAllSuccessfulUserMessages(long messageLatency) {
+ mLatencyOfSuccessfulUserMessages += messageLatency;
+ }
+
+ public void recordSuccessfulOutgoingDatagramStats(
+ @SatelliteManager.DatagramType int datagramType, long latency) {
+ try {
+ datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build());
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ data.incrementSuccessfulUserMessageCount();
+ if (data.mMaxLatency < latency) {
+ data.mMaxLatency = latency;
+ }
+ data.mLastMessageLatency = latency;
+ data.updateLatencyOfAllSuccessfulUserMessages(latency);
+ } catch (Exception e) {
+ Log.e("SatelliteSessionStats",
+ "Error while recordSuccessfulOutgoingDatagramStats: " + e.getMessage());
+ }
+ }
+
+ public int getCountOfSuccessfulOutgoingDatagram(
+ @SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.getOrDefault(datagramType,
+ new SatelliteSessionStats());
+ return data.getCountOfSuccessfulUserMessages();
+ }
+
+ public long getMaxLatency() {
+ return this.mMaxLatency;
+ }
+
+ public Long getLatencyOfAllSuccessfulUserMessages(
+ @SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.getOrDefault(datagramType,
+ new SatelliteSessionStats());
+ return data.getLatencyOfAllSuccessfulUserMessages();
+ }
+
+
+ public long getLastMessageLatency() {
+ return this.mLastMessageLatency;
+ }
+
+ public void addCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType,
+ @SatelliteManager.SatelliteResult int resultCode) {
+ try {
+ datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build());
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ data.incrementUnsuccessfulUserMessageCount();
+ if (resultCode == SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE) {
+ data.incrementTimedOutUserMessagesWaitingForConnection();
+ } else if (resultCode == SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT) {
+ data.incrementTimedOutUserMessagesWaitingForAck();
+ }
+ } catch (Exception e) {
+ Log.e("SatelliteSessionStats",
+ "Error while addCountOfUnsuccessfulUserMessages: " + e.getMessage());
+ }
+ }
+
+ public int getCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ return data.getCountOfUnsuccessfulUserMessages();
+ }
+
+ public int getCountOfTimedOutUserMessagesWaitingForConnection(
+ @SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ return data.getCountOfTimedOutUserMessagesWaitingForConnection();
+ }
+
+ public int getCountOfTimedOutUserMessagesWaitingForAck(
+ @SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ return data.getCountOfTimedOutUserMessagesWaitingForAck();
+ }
+
+ public int getCountOfUserMessagesInQueueToBeSent(
+ @SatelliteManager.DatagramType int datagramType) {
+ SatelliteSessionStats data = datagramStats.get(datagramType);
+ return data.getCountOfUserMessagesInQueueToBeSent();
+ }
+
+ public void clear() {
+ datagramStats.clear();
+ }
+
+ public Map<Integer, SatelliteSessionStats> getSatelliteSessionStats() {
+ return datagramStats;
+ }
+
+ public void setSatelliteSessionStats(Map<Integer, SatelliteSessionStats> sessionStats) {
+ this.datagramStats = sessionStats;
+ }
+
private void readFromParcel(Parcel in) {
mCountOfSuccessfulUserMessages = in.readInt();
mCountOfUnsuccessfulUserMessages = in.readInt();
mCountOfTimedOutUserMessagesWaitingForConnection = in.readInt();
mCountOfTimedOutUserMessagesWaitingForAck = in.readInt();
mCountOfUserMessagesInQueueToBeSent = in.readInt();
+ mLatencyOfSuccessfulUserMessages = in.readLong();
+ mMaxLatency = in.readLong();
+ mLastMessageLatency = in.readLong();
+
+ int size = in.readInt();
+ datagramStats = new HashMap<>();
+ for (int i = 0; i < size; i++) {
+ Integer key = in.readInt();
+ SatelliteSessionStats value = in.readParcelable(
+ SatelliteSessionStats.class.getClassLoader());
+ datagramStats.put(key, value);
+ }
}
/**
@@ -164,7 +352,10 @@ public class SatelliteSessionStats implements Parcelable {
private int mCountOfTimedOutUserMessagesWaitingForConnection;
private int mCountOfTimedOutUserMessagesWaitingForAck;
private int mCountOfUserMessagesInQueueToBeSent;
+ private long mLatencyOfSuccessfulUserMessages;
+ private long mMaxLatency;
+ private long mLastMessageLatency;
/**
* Sets countOfSuccessfulUserMessages value of {@link SatelliteSessionStats}
* and then returns the Builder class.
@@ -215,10 +406,28 @@ public class SatelliteSessionStats implements Parcelable {
return this;
}
+ @NonNull
+ public Builder setLatencyOfSuccessfulUserMessages(long latency) {
+ mLatencyOfSuccessfulUserMessages = latency;
+ return this;
+ }
+
+ @NonNull
+ public Builder setMaxLatency(long maxLatency) {
+ mMaxLatency = maxLatency;
+ return this;
+ }
+
+ @NonNull
+ public Builder setLastLatency(long lastLatency) {
+ mLastMessageLatency = lastLatency;
+ return this;
+ }
+
/** Returns SatelliteSessionStats object. */
@NonNull
public SatelliteSessionStats build() {
return new SatelliteSessionStats(this);
}
}
-}
+} \ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
index dbe5dddf8ce8..8427057fcaab 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
@@ -19,6 +19,7 @@ package android.telephony.satellite;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,7 +38,8 @@ import java.util.Objects;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class SatelliteSubscriberInfo implements Parcelable {
/** provision subscriberId */
@NonNull
@@ -50,10 +52,8 @@ public final class SatelliteSubscriberInfo implements Parcelable {
private int mSubId;
/** SubscriberId format is the ICCID. */
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final int ICCID = 0;
/** SubscriberId format is the 6 digit of IMSI + MSISDN. */
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final int IMSI_MSISDN = 1;
/** Type of subscriber id */
@@ -70,6 +70,9 @@ public final class SatelliteSubscriberInfo implements Parcelable {
readFromParcel(in);
}
+ /**
+ * @hide
+ */
public SatelliteSubscriberInfo(@NonNull Builder builder) {
this.mSubscriberId = builder.mSubscriberId;
this.mCarrierId = builder.mCarrierId;
@@ -80,11 +83,8 @@ public final class SatelliteSubscriberInfo implements Parcelable {
/**
* Builder class for constructing SatelliteSubscriberInfo objects
- *
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
- public static class Builder {
+ public static final class Builder {
@NonNull private String mSubscriberId;
private int mCarrierId;
@NonNull
@@ -95,17 +95,15 @@ public final class SatelliteSubscriberInfo implements Parcelable {
/**
* Set the SubscriberId and returns the Builder class.
- *
- * @hide
*/
- public Builder setSubscriberId(String subscriberId) {
+ @NonNull
+ public Builder setSubscriberId(@NonNull String subscriberId) {
mSubscriberId = subscriberId;
return this;
}
/**
* Set the CarrierId and returns the Builder class.
- * @hide
*/
@NonNull
public Builder setCarrierId(int carrierId) {
@@ -114,18 +112,19 @@ public final class SatelliteSubscriberInfo implements Parcelable {
}
/**
- * Set the niddApn and returns the Builder class.
- * @hide
+ * Set NIDD (Non IP Data) APN can be used for carrier roaming to satellite attachment
+ * and returns the Builder class.
+ *
+ * Refer specification 3GPP TS 23.501 V19.1.0 section 5.31.5
*/
@NonNull
- public Builder setNiddApn(String niddApn) {
+ public Builder setNiddApn(@NonNull String niddApn) {
mNiddApn = niddApn;
return this;
}
/**
* Set the subId and returns the Builder class.
- * @hide
*/
@NonNull
public Builder setSubId(int subId) {
@@ -135,7 +134,6 @@ public final class SatelliteSubscriberInfo implements Parcelable {
/**
* Set the SubscriberIdType and returns the Builder class.
- * @hide
*/
@NonNull
public Builder setSubscriberIdType(@SubscriberIdType int subscriberIdType) {
@@ -145,7 +143,6 @@ public final class SatelliteSubscriberInfo implements Parcelable {
/**
* Returns SatelliteSubscriberInfo object.
- * @hide
*/
@NonNull
public SatelliteSubscriberInfo build() {
@@ -153,11 +150,7 @@ public final class SatelliteSubscriberInfo implements Parcelable {
}
}
- /**
- * @hide
- */
@Override
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(mSubscriberId);
out.writeInt(mCarrierId);
@@ -166,7 +159,7 @@ public final class SatelliteSubscriberInfo implements Parcelable {
out.writeInt(mSubscriberIdType);
}
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+
public static final @android.annotation.NonNull Creator<SatelliteSubscriberInfo> CREATOR =
new Creator<SatelliteSubscriberInfo>() {
@Override
@@ -180,56 +173,45 @@ public final class SatelliteSubscriberInfo implements Parcelable {
}
};
- /**
- * @hide
- */
@Override
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public int describeContents() {
return 0;
}
/**
- * @return provision subscriberId.
- * @hide
+ * Return subscriberId which is used to register with satellite gateway service
+ * during provisioning.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @NonNull
public String getSubscriberId() {
return mSubscriberId;
}
/**
- * @return carrierId.
- * @hide
+ * Return carrierId of the subscription used for provisioning.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public int getCarrierId() {
return mCarrierId;
}
/**
- * @return niddApn.
- * @hide
+ * Return the NIDD(Non IP Data) APN which is used for carrier roaming to satellite attachment.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @NonNull
public String getNiddApn() {
return mNiddApn;
}
/**
- * @return subId.
- * @hide
+ * Return the subscriptionId of the subscription which is used for satellite attachment.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public int getSubId() {
return mSubId;
}
/**
- * @return subscriberIdType.
- * @hide
+ * Return the type of subscriberId used for provisioning.
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public @SubscriberIdType int getSubscriberIdType() {
return mSubscriberIdType;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
index 08ef3f2d8d12..fb4f89ded547 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
@@ -18,6 +18,7 @@ package android.telephony.satellite;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,49 +31,49 @@ import java.util.Objects;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatelliteSubscriberProvisionStatus implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatelliteSubscriberProvisionStatus implements Parcelable {
private SatelliteSubscriberInfo mSubscriberInfo;
/** {@code true} mean the satellite subscriber is provisioned, {@code false} otherwise. */
- private boolean mProvisionStatus;
+ private boolean mProvisioned;
+ /**
+ * @hide
+ */
public SatelliteSubscriberProvisionStatus(@NonNull Builder builder) {
mSubscriberInfo = builder.mSubscriberInfo;
- mProvisionStatus = builder.mProvisionStatus;
+ mProvisioned = builder.mProvisioned;
}
/**
* Builder class for constructing SatelliteSubscriberProvisionStatus objects
- *
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
- public static class Builder {
+ public static final class Builder {
private SatelliteSubscriberInfo mSubscriberInfo;
- private boolean mProvisionStatus;
+ private boolean mProvisioned;
/**
* Set the SatelliteSubscriberInfo and returns the Builder class.
- * @hide
*/
- public Builder setSatelliteSubscriberInfo(SatelliteSubscriberInfo satelliteSubscriberInfo) {
+ @NonNull
+ public Builder setSatelliteSubscriberInfo(
+ @NonNull SatelliteSubscriberInfo satelliteSubscriberInfo) {
mSubscriberInfo = satelliteSubscriberInfo;
return this;
}
/**
* Set the SatelliteSubscriberInfo's provisionStatus and returns the Builder class.
- * @hide
*/
@NonNull
- public Builder setProvisionStatus(boolean provisionStatus) {
- mProvisionStatus = provisionStatus;
+ public Builder setProvisioned(boolean provisioned) {
+ mProvisioned = provisioned;
return this;
}
/**
* Returns SatelliteSubscriberProvisionStatus object.
- * @hide
*/
@NonNull
public SatelliteSubscriberProvisionStatus build() {
@@ -84,17 +85,12 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
readFromParcel(in);
}
- /**
- * @hide
- */
@Override
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeParcelable(mSubscriberInfo, flags);
- out.writeBoolean(mProvisionStatus);
+ out.writeBoolean(mProvisioned);
}
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final @android.annotation.NonNull Creator<SatelliteSubscriberProvisionStatus>
CREATOR =
new Creator<SatelliteSubscriberProvisionStatus>() {
@@ -109,11 +105,7 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
}
};
- /**
- * @hide
- */
@Override
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public int describeContents() {
return 0;
}
@@ -121,9 +113,7 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
/**
* SatelliteSubscriberInfo that has a provisioning state.
* @return SatelliteSubscriberInfo.
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public @NonNull SatelliteSubscriberInfo getSatelliteSubscriberInfo() {
return mSubscriberInfo;
}
@@ -131,11 +121,9 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
/**
* SatelliteSubscriberInfo's provisioning state.
* @return {@code true} means provisioning. {@code false} means deprovisioning.
- * @hide
*/
- @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
- public @NonNull boolean getProvisionStatus() {
- return mProvisionStatus;
+ public boolean isProvisioned() {
+ return mProvisioned;
}
@NonNull
@@ -148,13 +136,13 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
sb.append(",");
sb.append("ProvisionStatus:");
- sb.append(mProvisionStatus);
+ sb.append(mProvisioned);
return sb.toString();
}
@Override
public int hashCode() {
- return Objects.hash(mSubscriberInfo, mProvisionStatus);
+ return Objects.hash(mSubscriberInfo, mProvisioned);
}
@Override
@@ -163,12 +151,12 @@ public class SatelliteSubscriberProvisionStatus implements Parcelable {
if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false;
SatelliteSubscriberProvisionStatus that = (SatelliteSubscriberProvisionStatus) o;
return Objects.equals(mSubscriberInfo, that.mSubscriberInfo)
- && mProvisionStatus == that.mProvisionStatus;
+ && mProvisioned == that.mProvisioned;
}
private void readFromParcel(Parcel in) {
mSubscriberInfo = in.readParcelable(SatelliteSubscriberInfo.class.getClassLoader(),
SatelliteSubscriberInfo.class);
- mProvisionStatus = in.readBoolean();
+ mProvisioned = in.readBoolean();
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
index 2ef19f8dd69e..1897a9b7a428 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
@@ -16,10 +16,14 @@
package android.telephony.satellite;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
@@ -28,6 +32,8 @@ import java.util.Objects;
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class SatelliteSubscriptionInfo implements Parcelable {
/**
* The ICC ID used for satellite attachment.
@@ -39,6 +45,9 @@ public final class SatelliteSubscriptionInfo implements Parcelable {
*/
@NonNull private final String mNiddApn;
+ /**
+ * @hide
+ */
public SatelliteSubscriptionInfo(@NonNull String iccId, @NonNull String niddApn) {
mIccId = iccId;
mNiddApn = niddApn;
@@ -60,7 +69,8 @@ public final class SatelliteSubscriptionInfo implements Parcelable {
dest.writeString8(mNiddApn);
}
- @NonNull public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
+ @NonNull
+ public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
@Override
public SatelliteSubscriptionInfo createFromParcel(Parcel in) {
return new SatelliteSubscriptionInfo(in);
@@ -94,11 +104,17 @@ public final class SatelliteSubscriptionInfo implements Parcelable {
return Objects.hash(getIccId(), getNiddApn());
}
+ /**
+ * Get ICC ID used for satellite attachment.
+ */
@NonNull
public String getIccId() {
return mIccId;
}
+ /**
+ * Get the NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+ */
@NonNull
public String getNiddApn() {
return mNiddApn;
diff --git a/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
index 7e19bd13140d..5487eb6ac942 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
import com.android.internal.telephony.flags.Flags;
@@ -25,16 +26,14 @@ import com.android.internal.telephony.flags.Flags;
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public interface SatelliteSupportedStateCallback {
/**
* Called when satellite supported state changes.
*
* @param supported The new supported state. {@code true} means satellite is supported,
* {@code false} means satellite is not supported.
- *
- * @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
void onSatelliteSupportedStateChanged(boolean supported);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 046ae5fdeb3c..b5dfb631609c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -58,12 +58,11 @@ public interface SatelliteTransmissionUpdateCallback {
* @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
- *
- * @hide
*/
- void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+ default void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
@SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
- @SatelliteManager.SatelliteResult int errorCode);
+ @SatelliteManager.SatelliteResult int errorCode) {}
/**
* Called when satellite datagram receive state changed.
*
@@ -80,8 +79,7 @@ public interface SatelliteTransmissionUpdateCallback {
* Called when framework receives a request to send a datagram.
*
* @param datagramType The type of the requested datagram.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
default void onSendDatagramRequested(@SatelliteManager.DatagramType int datagramType) {}
}
diff --git a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
index d8965547a20e..76caef3c9c7c 100644
--- a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
+++ b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
@@ -16,11 +16,18 @@
package android.telephony.satellite;
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
/**
* A callback class for selected satellite subscription changed events.
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public interface SelectedNbIotSatelliteSubscriptionCallback {
/**
* Called when the selected satellite subscription has changed.
diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
index c2ac44f0067e..61e1e5fa8e7e 100644
--- a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
+++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
@@ -16,19 +16,26 @@
package android.telephony.satellite;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.IntArray;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
/**
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public final class SystemSelectionSpecifier implements Parcelable {
/** Network plmn associated with channel information. */
@@ -188,26 +195,41 @@ public final class SystemSelectionSpecifier implements Parcelable {
return Objects.hash(mMccMnc, mBands, mEarfcns);
}
+ /** Return network plmn associated with channel information. */
@NonNull public String getMccMnc() {
return mMccMnc;
}
- @NonNull public IntArray getBands() {
- return mBands;
+ /**
+ * Return the frequency bands to scan.
+ * Maximum length of the vector is 8.
+ */
+ @NonNull public int[] getBands() {
+ return mBands.toArray();
}
- @NonNull public IntArray getEarfcns() {
- return mEarfcns;
+ /**
+ * Return the radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+ * Maximum length of the vector is 32.
+ */
+ @NonNull public int[] getEarfcns() {
+ return mEarfcns.toArray();
}
+ /** Return the list of satellites configured for the current location. */
@NonNull
- public SatelliteInfo[] getSatelliteInfos() {
- return mSatelliteInfos;
+ public List<SatelliteInfo> getSatelliteInfos() {
+ return Arrays.stream(mSatelliteInfos).toList();
}
+ /**
+ * Return the list of tag IDs associated with the current location
+ * Tag Ids are generic IDs an OEM can configure. Each tag ID can map to a region which can be
+ * used by OEM to identify proprietary configuration for that region.
+ */
@NonNull
- public IntArray getTagIds() {
- return mTagIds;
+ public int[] getTagIds() {
+ return mTagIds.toArray();
}
private void readFromParcel(Parcel in) {
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 974cc14ae444..71327dcfc3b1 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -83,6 +83,12 @@ interface IPhoneSubInfo {
String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
String callingFeatureId);
+ /**
+ * Retrieves the Group Identifier Level1 for GSM phones of a subId.
+ */
+ String getGroupIdLevel2ForSubscriber(int subId, String callingPackage,
+ String callingFeatureId);
+
/** @deprecared Use {@link getIccSerialNumberWithFeature(String, String)} instead */
@UnsupportedAppUsage
String getIccSerialNumber(String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b739666c3f1b..da7669fd81ad 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3168,7 +3168,7 @@ interface ITelephony {
*/
boolean setSatelliteAccessControlOverlayConfigs(in boolean reset, in boolean isAllowed,
in String s2CellFile, in long locationFreshDurationNanos,
- in List<String> satelliteCountryCodes);
+ in List<String> satelliteCountryCodes, String satelliteAccessConfigurationFile);
/**
* This API can be used in only testing to override oem-enabled satellite provision status.
@@ -3471,6 +3471,17 @@ interface ITelephony {
void requestSatelliteSubscriberProvisionStatus(in ResultReceiver result);
/**
+ * Request to get the name to display for Satellite subscription.
+ *
+ * @param receiver The result receiver that returns the diplay name to be used for the satellite
+ * subscription.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestSatelliteDisplayName(in ResultReceiver receiver);
+
+ /**
* Deliver the list of provisioned satellite subscriber infos.
*
* @param list The list of provisioned satellite subscriber infos.
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index b7dd4df1c843..461d1579ea45 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -549,6 +549,9 @@ public interface RILConstants {
int RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED = 248;
int RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED = 249;
int RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT = 250;
+ int RIL_REQUEST_SET_SATELLITE_PLMN = 251;
+ int RIL_REQUEST_SET_SATELLITE_ENABLED_FOR_CARRIER = 252;
+ int RIL_REQUEST_IS_SATELLITE_ENABLED_FOR_CARRIER = 253;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index e14e5fea001f..1c8386add1b1 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -31,6 +31,8 @@ java_test_host {
],
static_libs: [
"truth",
+ "flag-junit-host",
+ "android.app.flags-aconfig-java-host",
],
device_common_data: [
":BinaryTransparencyTestApp",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6e5f08a11ed8..6d8dbcb5c963 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -24,6 +24,9 @@ import static org.junit.Assert.fail;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -34,6 +37,7 @@ import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,6 +53,10 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
/** Waiting time for the job to be scheduled */
private static final int JOB_CREATION_MAX_SECONDS = 30;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
@Before
public void setUp() throws Exception {
cancelPendingJob();
@@ -123,6 +131,7 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
}
}
+ @RequiresFlagsDisabled(android.app.Flags.FLAG_BACKGROUND_INSTALL_CONTROL_CALLBACK_API)
@Test
public void testPreloadUpdateTriggersJobScheduling() throws Exception {
try {
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 8b65efdfb5f9..685ae9a5fef2 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 3382c1e227b3..5f92d7fe830b 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 e941e79faea3..1b90e99a8ba2 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 4e06dca17fe2..ffdbb02984a7 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 0cadd68597b6..12670cda74b2 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 f32e8bed85ef..e2ac5a9579ae 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 68ae4f1f7f4f..1a4feb6e9eca 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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 ec186723b4a4..481a8bb66fee 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<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"/>
<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/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
new file mode 100644
index 000000000000..6573c2c83f20
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class BottomHalfPipAppHelper(
+ instrumentation: Instrumentation,
+ private val useLaunchingActivity: Boolean = false,
+) : PipAppHelper(
+ instrumentation,
+ appName = ActivityOptions.BottomHalfPip.LABEL,
+ componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
+ .toFlickerComponent()
+) {
+ override val openAppIntent: Intent
+ get() = super.openAppIntent.apply {
+ component = if (useLaunchingActivity) {
+ ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
+ } else {
+ ActivityOptions.BottomHalfPip.COMPONENT
+ }
+ }
+} \ No newline at end of file
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 e0900a64c52f..c1c5dc66bac1 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
@@ -39,6 +39,7 @@ import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH
import java.time.Duration
+import kotlin.math.abs
/**
* Wrapper class around App helper classes. This class adds functionality to the apps that the
@@ -222,11 +223,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
val expectedRect = Rect(displayRect).apply {
if (toLeft) right -= expectedWidth else left += expectedWidth
}
-
- wmHelper
- .StateSyncBuilder()
+ wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
- .withSurfaceVisibleRegion(this, Region(expectedRect))
+ .withSurfaceMatchingVisibleRegion(
+ this,
+ Region(expectedRect),
+ { surfaceRegion, expectedRegion ->
+ areSnapWindowRegionsMatchingWithinThreshold(
+ surfaceRegion, expectedRegion, toLeft
+ )
+ })
.waitForAndVerify()
}
@@ -460,6 +466,33 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) =
wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM
+ private fun areSnapWindowRegionsMatchingWithinThreshold(
+ surfaceRegion: Region, expectedRegion: Region, toLeft: Boolean
+ ): Boolean {
+ val surfaceBounds = surfaceRegion.bounds
+ val expectedBounds = expectedRegion.bounds
+ // If snapped to left, right bounds will be cut off by the center divider.
+ // Else if snapped to right, the left bounds will be cut off.
+ val leftSideMatching: Boolean
+ val rightSideMatching: Boolean
+ if (toLeft) {
+ leftSideMatching = surfaceBounds.left == expectedBounds.left
+ rightSideMatching =
+ abs(surfaceBounds.right - expectedBounds.right) <=
+ surfaceBounds.right * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+ } else {
+ leftSideMatching =
+ abs(surfaceBounds.left - expectedBounds.left) <=
+ surfaceBounds.left * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+ rightSideMatching = surfaceBounds.right == expectedBounds.right
+ }
+
+ return surfaceBounds.top == expectedBounds.top &&
+ surfaceBounds.bottom == expectedBounds.bottom &&
+ leftSideMatching &&
+ rightSideMatching
+ }
+
private companion object {
val TIMEOUT: Duration = Duration.ofSeconds(3)
const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -475,5 +508,12 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ // In DesktopMode, window snap can be done with just a single window. In this case, the
+ // divider tiling between left and right window won't be shown, and hence its states are not
+ // obtainable in test.
+ // As the test should just focus on ensuring window goes to one side of the screen, an
+ // acceptable approach is to ensure snapped window still fills > 95% of either side of the
+ // screen.
+ const val SNAP_WINDOW_MAX_THRESHOLD_DIFF = 0.05
}
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
deleted file mode 100644
index eeee7b4dfc6b..000000000000
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.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.server.wm.flicker.helpers;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import androidx.annotation.Nullable;
-
-/**
- * Injects gestures given an {@link Instrumentation} object.
- */
-public class GestureHelper {
- // Inserted after each motion event injection.
- private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
-
- private final UiAutomation mUiAutomation;
-
- /**
- * Primary pointer should be cached here for separate release
- */
- @Nullable private PointerProperties mPrimaryPtrProp;
- @Nullable private PointerCoords mPrimaryPtrCoord;
- private long mPrimaryPtrDownTime;
-
- /**
- * A pair of floating point values.
- */
- public static class Tuple {
- public float x;
- public float y;
-
- public Tuple(float x, float y) {
- this.x = x;
- this.y = y;
- }
- }
-
- public GestureHelper(Instrumentation instrumentation) {
- mUiAutomation = instrumentation.getUiAutomation();
- }
-
- /**
- * Injects a series of {@link MotionEvent}s to simulate tapping.
- *
- * @param point coordinates of pointer to tap
- * @param times the number of times to tap
- */
- public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
- PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
-
- for (int i = 0; i <= times; i++) {
- // If already tapped, inject delay in between movements
- if (times > 0) {
- SystemClock.sleep(50L);
- }
- if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
- return false;
- }
- // Delay before releasing tap
- SystemClock.sleep(100L);
- if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
- *
- * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
- * will be cached for potential release later on by {@code releasePrimaryPointer()}
- *
- * @param startPoint initial coordinates of the primary pointer
- * @param endPoint final coordinates of the primary pointer
- * @param steps number of steps to take to animate dragging
- * @return true if gesture is injected successfully
- */
- public boolean dragWithoutRelease(@NonNull Tuple startPoint,
- @NonNull Tuple endPoint, int steps) {
- PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1);
-
- PointerProperties[] ptrProps = new PointerProperties[] { ptrProp };
- PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord };
-
- long downTime = SystemClock.uptimeMillis();
-
- if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) {
- return false;
- }
-
- // cache the primary pointer info for later potential release
- mPrimaryPtrProp = ptrProp;
- mPrimaryPtrCoord = ptrCoord;
- mPrimaryPtrDownTime = downTime;
-
- return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps);
- }
-
- /**
- * Release primary pointer if previous gesture has cached the primary pointer info.
- *
- * @return true if the release was injected successfully
- */
- public boolean releasePrimaryPointer() {
- if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) {
- return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime);
- }
-
- return false;
- }
-
- /**
- * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
- *
- * @param startPoint1 initial coordinates of the first pointer
- * @param startPoint2 initial coordinates of the second pointer
- * @param endPoint1 final coordinates of the first pointer
- * @param endPoint2 final coordinates of the second pointer
- * @param steps number of steps to take to animate pinching
- * @return true if gesture is injected successfully
- */
- public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
- @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
- PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
-
- PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
- PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
-
- PointerProperties[] ptrProps = new PointerProperties[] {
- ptrProp1, ptrProp2
- };
-
- PointerCoords[] ptrCoords = new PointerCoords[] {
- ptrCoord1, ptrCoord2
- };
-
- long downTime = SystemClock.uptimeMillis();
-
- if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
- return false;
- }
-
- if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
- return false;
- }
-
- if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
- downTime, steps)) {
- return false;
- }
-
- if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
- return false;
- }
-
- return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
- }
-
- private boolean primaryPointerDown(@NonNull PointerProperties prop,
- @NonNull PointerCoords coord, long downTime) {
- MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
- new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
- return injectEventSync(event);
- }
-
- private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, long downTime, int index) {
- // at least 2 pointers are needed
- if (props.length != coords.length || coords.length < 2) {
- return false;
- }
-
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
- + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
- return injectEventSync(event);
- }
-
- private boolean movePointers(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
- // the number of endpoints should be the same as the number of pointers
- if (props.length != coords.length || coords.length != endPoints.length) {
- return false;
- }
-
- // prevent division by 0 and negative number of steps
- if (steps < 1) {
- steps = 1;
- }
-
- // save the starting points before updating any pointers
- Tuple[] startPoints = new Tuple[coords.length];
-
- for (int i = 0; i < coords.length; i++) {
- startPoints[i] = new Tuple(coords[i].x, coords[i].y);
- }
-
- MotionEvent event;
- long eventTime;
-
- for (int i = 0; i < steps; i++) {
- // inject a delay between movements
- SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
-
- // update the coordinates
- for (int j = 0; j < coords.length; j++) {
- coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
- coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
- }
-
- eventTime = SystemClock.uptimeMillis();
-
- event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
- coords.length, props, coords);
-
- boolean didInject = injectEventSync(event);
-
- if (!didInject) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean primaryPointerUp(@NonNull PointerProperties prop,
- @NonNull PointerCoords coord, long downTime) {
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
- new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
- return injectEventSync(event);
- }
-
- private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, long downTime, int index) {
- // at least 2 pointers are needed
- if (props.length != coords.length || coords.length < 2) {
- return false;
- }
-
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
- + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
- return injectEventSync(event);
- }
-
- private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
- PointerCoords ptrCoord = new PointerCoords();
- ptrCoord.x = x;
- ptrCoord.y = y;
- ptrCoord.pressure = pressure;
- ptrCoord.size = size;
- return ptrCoord;
- }
-
- private PointerProperties getPointerProp(int id, int toolType) {
- PointerProperties ptrProp = new PointerProperties();
- ptrProp.id = id;
- ptrProp.toolType = toolType;
- return ptrProp;
- }
-
- private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
- int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
- return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
- ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
- 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
- }
-
- private boolean injectEventSync(InputEvent event) {
- return mUiAutomation.injectInputEvent(event, true);
- }
-}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index fd13d14074d4..d5334cbd541c 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -21,6 +21,7 @@ import android.graphics.Rect
import android.graphics.Region
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
+import android.tools.helpers.GestureHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -38,7 +39,8 @@ constructor(
ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
) : StandardAppHelper(instr, launcherName, component) {
- private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
+ private val gestureHelper: GestureHelper =
+ GestureHelper(instrumentation)
fun clickRestart(wmHelper: WindowManagerStateHelper) {
val restartButton =
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index db4838ee6092..de17bf422c0c 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -18,29 +18,26 @@ package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.content.Intent
-import android.graphics.Rect
import android.graphics.Region
import android.media.session.MediaController
import android.media.session.MediaSessionManager
-import android.tools.datatypes.coversMoreThan
-import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.apphelpers.BasePipAppHelper
import android.tools.helpers.FIND_TIMEOUT
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.ConditionsFactory
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.parsers.toFlickerComponent
-import android.util.Log
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
-open class PipAppHelper(instrumentation: Instrumentation) :
- StandardAppHelper(
- instrumentation,
- ActivityOptions.Pip.LABEL,
- ActivityOptions.Pip.COMPONENT.toFlickerComponent()
- ) {
+open class PipAppHelper(
+ instrumentation: Instrumentation,
+ appName: String = ActivityOptions.Pip.LABEL,
+ componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(),
+) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) {
private val mediaSessionManager: MediaSessionManager
get() =
context.getSystemService(MediaSessionManager::class.java)
@@ -52,189 +49,6 @@ open class PipAppHelper(instrumentation: Instrumentation) :
it.packageName == packageName
}
- private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
-
- open fun clickObject(resId: String) {
- val selector = By.res(packageName, resId)
- val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
- obj.click()
- }
-
- /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
- fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = Rect(getWindowRect(wmHelper))
-
- // initial pointer at the center of the window
- val initialCoord =
- GestureHelper.Tuple(
- initWindowRect.centerX().toFloat(),
- initWindowRect.centerY().toFloat()
- )
-
- // the offset to the right (or left) of the window center to drag the window to
- val offset = 50
-
- // the actual final x coordinate with the offset included;
- // if the pip window is closer to the right edge of the display the offset is negative
- // otherwise the offset is positive
- val endX =
- initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
- val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
-
- // drag to the final coordinate
- gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps)
- }
-
- /**
- * Releases the primary pointer.
- *
- * Injects the release of the primary pointer if the primary pointer info was cached after
- * another gesture was injected without pointer release.
- */
- fun releasePipAfterDragging() {
- gestureHelper.releasePrimaryPointer()
- }
-
- /**
- * Drags the PIP window away from the screen edge while not crossing the display center.
- *
- * @throws IllegalStateException if default display bounds are not available
- */
- fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = Rect(getWindowRect(wmHelper))
-
- // initial pointer at the center of the window
- val startX = initWindowRect.centerX()
- val y = initWindowRect.centerY()
-
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
-
- // the offset to the right (or left) of the display center to drag the window to
- val offset = 20
-
- // the actual final x coordinate with the offset included;
- // if the pip window is closer to the right edge of the display the offset is positive
- // otherwise the offset is negative
- val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1)
-
- // drag the window to the left but not beyond the center of the display
- uiDevice.drag(startX, y, endX, y, steps)
- }
-
- /**
- * Returns true if PIP window is closer to the right edge of the display than left.
- *
- * @throws IllegalStateException if default display bounds are not available
- */
- fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
- val windowRect = getWindowRect(wmHelper)
-
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
-
- return windowRect.centerX() > displayRect.centerX()
- }
-
- /**
- * Expands the PIP window by using the pinch out gesture.
- *
- * @param percent The percentage by which to increase the pip window size.
- * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
- */
- fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
- // the percentage must be between 0.0f and 1.0f
- if (percent <= 0.0f || percent > 1.0f) {
- throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
- }
-
- val windowRect = getWindowRect(wmHelper)
-
- // first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
- // second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
- // horizontal distance the window should increase by
- val distIncrease = windowRect.width() * percent
-
- // final x-coordinates
- val finalLeftX = initLeftX - (distIncrease / 2)
- val finalRightX = initRightX + (distIncrease / 2)
-
- // y-coordinate is the same throughout this animation
- val yCoord = windowRect.centerY().toFloat()
-
- var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
- // if distance per step is at least 1, then we can use the number of steps requested
- if (distIncrease.toInt() / (steps * 2) >= 1) {
- adjustedSteps = steps
- }
-
- // if the distance per step is less than 1, carry out the animation in two steps
- gestureHelper.pinch(
- GestureHelper.Tuple(initLeftX, yCoord),
- GestureHelper.Tuple(initRightX, yCoord),
- GestureHelper.Tuple(finalLeftX, yCoord),
- GestureHelper.Tuple(finalRightX, yCoord),
- adjustedSteps
- )
-
- waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
- }
-
- /**
- * Minimizes the PIP window by using the pinch in gesture.
- *
- * @param percent The percentage by which to decrease the pip window size.
- * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
- */
- fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
- // the percentage must be between 0.0f and 1.0f
- if (percent <= 0.0f || percent > 1.0f) {
- throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
- }
-
- val windowRect = getWindowRect(wmHelper)
-
- // first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
- // second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
- // decrease by the distance specified through the percentage
- val distDecrease = windowRect.width() * percent
-
- // get the final x-coordinates and make sure they are not passing the center of the window
- val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
- val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
-
- // y-coordinate is the same throughout this animation
- val yCoord = windowRect.centerY().toFloat()
-
- var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
- // if distance per step is at least 1, then we can use the number of steps requested
- if (distDecrease.toInt() / (steps * 2) >= 1) {
- adjustedSteps = steps
- }
-
- // if the distance per step is less than 1, carry out the animation in two steps
- gestureHelper.pinch(
- GestureHelper.Tuple(initLeftX, yCoord),
- GestureHelper.Tuple(initRightX, yCoord),
- GestureHelper.Tuple(finalLeftX, yCoord),
- GestureHelper.Tuple(finalRightX, yCoord),
- adjustedSteps
- )
-
- waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect))
- }
-
/**
* Launches the app through an intent instead of interacting with the launcher and waits until
* the app window is in PIP mode
@@ -331,126 +145,6 @@ open class PipAppHelper(instrumentation: Instrumentation) :
closePipWindow(WindowManagerStateHelper(instrumentation))
}
- /** Returns the pip window bounds. */
- fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
- val windowRegion = wmHelper.getWindowRegion(this)
- require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
- return windowRegion.bounds
- }
-
- /** Taps the pip window and dismisses it by clicking on the X button. */
- open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- val dismissPipObject =
- uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found")
- val dismissButtonBounds = dismissPipObject.visibleBounds
- uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-
- // Wait for animation to complete.
- wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
- }
-
- open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- }
-
- /** Close the pip window by pressing the expand button */
- fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the expand button
- val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
- uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
- val expandPipObject =
- uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found")
- val expandButtonBounds = expandPipObject.visibleBounds
- uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
- wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify()
- }
-
- /** Double click on the PIP window to expand it */
- fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- Log.d(TAG, "First click")
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- Log.d(TAG, "Second click")
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- Log.d(TAG, "Wait for app transition to end")
- wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
- waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
- }
-
- private fun waitForPipWindowToExpandFrom(
- wmHelper: WindowManagerStateHelper,
- windowRect: Region
- ) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowExpanded") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
- val pipRegion = pipAppWindow.frameRegion
- return@add pipRegion.coversMoreThan(windowRect)
- }
- .waitForAndVerify()
- }
-
- private fun waitForPipWindowToMinimizeFrom(
- wmHelper: WindowManagerStateHelper,
- windowRect: Region
- ) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowMinimized") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- Log.d(TAG, "window " + pipAppWindow)
- if (pipAppWindow == null) return@add false
- val pipRegion = pipAppWindow.frameRegion
- Log.d(
- TAG,
- "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion)
- )
- return@add windowRect.coversMoreThan(pipRegion)
- }
- .waitForAndVerify()
- }
-
- /**
- * Waits until the PIP window snaps horizontally to the provided bounds.
- *
- * @param finalBounds the bounds to wait for PIP window to snap to
- */
- fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowSnapped") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
- val pipRegionBounds = pipAppWindow.frameRegion.bounds
- return@add pipRegionBounds.left == finalBounds.left &&
- pipRegionBounds.right == finalBounds.right
- }
- .add(ConditionsFactory.isWMStateComplete())
- .waitForAndVerify()
- }
-
companion object {
private const val TAG = "PipAppHelper"
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
@@ -459,8 +153,5 @@ open class PipAppHelper(instrumentation: Instrumentation) :
private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
private const val SOURCE_RECT_HINT = "set_source_rect_hint"
- // minimum number of steps to take, when animating gestures, needs to be 2
- // so that there is at least a single intermediate layer that flicker tests can check
- private const val MIN_STEPS_TO_ANIMATE = 2
}
-}
+} \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9ce8e807f612..7c24a4adca3d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,6 +347,27 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
+ <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>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BottomHalfPipActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/TranslucentTheme"
+ android:label="BottomHalfPipActivity"
+ android:exported="true">
+ </activity>
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 47d113717ae0..837d050b73ff 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,6 +62,12 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
+ <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 73625da9dfa5..0c1ac9951d32 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -241,6 +241,21 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".PipActivity");
}
+ public static class BottomHalfPip {
+ public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
+ // Test App > Bottom Half PIP Activity
+ public static final String LABEL = "BottomHalfPipActivity";
+
+ // Use the bottom half layout for PIP Activity
+ public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
+
+ public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
+
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
+ }
+
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
new file mode 100644
index 000000000000..3d4865572486
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BottomHalfPipActivity extends PipActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.TranslucentTheme);
+ updateLayout();
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateLayout();
+ }
+
+ /**
+ * Sets to match parent layout if the activity is
+ * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
+ * layout.
+ *
+ * @see #setToBottomHalfMode(boolean)
+ */
+ private void updateLayout() {
+ setToBottomHalfMode(!isInPictureInPictureMode());
+ }
+
+ /**
+ * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
+ * [LayoutParams.MATCH_PARENT] layout.
+ */
+ private void setToBottomHalfMode(boolean useBottomHalfLayout) {
+ final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ if (useBottomHalfLayout) {
+ final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
+ .height();
+ attrs.y = taskHeight / 2;
+ attrs.height = taskHeight / 2;
+ } else {
+ attrs.y = 0;
+ attrs.height = LayoutParams.MATCH_PARENT;
+ }
+ getWindow().setAttributes(attrs);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
new file mode 100644
index 000000000000..d9d4361411bb
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class BottomHalfPipLaunchingActivity extends SimpleActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = new Intent(this, BottomHalfPipActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/InputDataStoreTests.kt b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt
new file mode 100644
index 000000000000..78c828bafd8f
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt
@@ -0,0 +1,504 @@
+/*
+ * Copyright 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.input
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.hardware.input.AppLaunchData
+import android.hardware.input.InputGestureData
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.util.AtomicFile
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+
+/**
+ * Tests for {@link InputDataStore}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDataStoreTests
+ */
+@Presubmit
+class InputDataStoreTests {
+
+ companion object {
+ const val USER_ID = 1
+ }
+
+ private lateinit var context: Context
+ private lateinit var inputDataStore: InputDataStore
+ private lateinit var tempFile: File
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ setupInputDataStore()
+ }
+
+ private fun setupInputDataStore() {
+ tempFile = File.createTempFile("input_gestures", ".xml")
+ inputDataStore = InputDataStore(object : InputDataStore.FileInjector("input_gestures") {
+ private val atomicFile: AtomicFile = AtomicFile(tempFile)
+
+ override fun openRead(userId: Int): InputStream? {
+ return atomicFile.openRead()
+ }
+
+ override fun startWrite(userId: Int): FileOutputStream? {
+ return atomicFile.startWrite()
+ }
+
+ override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) {
+ if (success) {
+ atomicFile.finishWrite(fos)
+ } else {
+ atomicFile.failWrite(fos)
+ }
+ }
+ })
+ }
+
+ private fun getPrintableXml(inputGestures: List<InputGestureData>): String {
+ val outputStream = ByteArrayOutputStream()
+ inputDataStore.writeInputGestureXml(outputStream, true, inputGestures)
+ return outputStream.toString(StandardCharsets.UTF_8).trimIndent()
+ }
+
+ @Test
+ fun saveToDiskKeyGesturesOnly() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskTouchpadGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskAppLaunchGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER))
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS))
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(
+ AppLaunchData.createLaunchDataForComponent(
+ "com.test",
+ "com.test.BookmarkTest"
+ )
+ )
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskCombinedGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_9,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS))
+ .build(),
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun validXmlParse() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun missingTriggerData() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidKeycode() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9999999" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTriggerName() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <invalid_trigger_name touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTouchpadGestureType() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="9999" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun emptyInputGestureList() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ listOf(),
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTag() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <invalid_tag_name>
+ </invalid_tag_name>
+ </root>""".trimIndent()
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ listOf(),
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+} \ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 6eb00457a1a6..43844f6514e8 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -156,8 +156,7 @@ class InputManagerServiceTests {
}
override fun getKeyboardBacklightController(
- nativeService: NativeInputManagerService?,
- dataStore: PersistentDataStore?
+ nativeService: NativeInputManagerService?
): InputManagerService.KeyboardBacklightControllerInterface {
return kbdController
}
@@ -216,6 +215,7 @@ class InputManagerServiceTests {
verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
+ verify(native).setTouchpadSystemGesturesEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 36a89f95aa6f..c6d281914f2c 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -40,6 +40,7 @@ import android.os.test.TestLooper
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
+import android.util.AtomicFile
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
@@ -50,6 +51,9 @@ import com.android.internal.R
import com.android.internal.annotations.Keep
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
import junitparams.JUnitParamsRunner
import junitparams.Parameters
import org.junit.After
@@ -123,6 +127,8 @@ class KeyGestureControllerTests {
private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
+ private lateinit var tempFile: File
+ private lateinit var inputDataStore: InputDataStore
private var events = mutableListOf<KeyGestureEvent>()
@Before
@@ -133,6 +139,31 @@ class KeyGestureControllerTests {
setupBehaviors()
testLooper = TestLooper()
currentPid = Process.myPid()
+ tempFile = File.createTempFile("input_gestures", ".xml")
+ inputDataStore =
+ InputDataStore(object : InputDataStore.FileInjector("input_gestures.xml") {
+ private val atomicFile: AtomicFile = AtomicFile(tempFile)
+
+ override fun openRead(userId: Int): InputStream? {
+ return atomicFile.openRead()
+ }
+
+ override fun startWrite(userId: Int): FileOutputStream? {
+ return atomicFile.startWrite()
+ }
+
+ override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) {
+ if (success) {
+ atomicFile.finishWrite(fos)
+ } else {
+ atomicFile.failWrite(fos)
+ }
+ }
+
+ override fun getAtomicFileForUserId(userId: Int): AtomicFile {
+ return atomicFile
+ }
+ })
}
@After
@@ -174,10 +205,12 @@ class KeyGestureControllerTests {
}
private fun setupKeyGestureController() {
- keyGestureController = KeyGestureController(context, testLooper.looper)
+ keyGestureController =
+ KeyGestureController(context, testLooper.looper, inputDataStore)
Mockito.`when`(iInputManager.getAppLaunchBookmarks())
.thenReturn(keyGestureController.appLaunchBookmarks)
keyGestureController.systemRunning()
+ testLooper.dispatchAll()
}
private fun notifyHomeGestureCompleted() {
@@ -1424,6 +1457,45 @@ class KeyGestureControllerTests {
testKeyGestureInternal(test)
}
+ @Test
+ @Parameters(method = "customInputGesturesTestArguments")
+ fun testCustomKeyGesturesSavedAndLoadedByController(test: TestData) {
+ val userId = 10
+ setupKeyGestureController()
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
+ )
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ val inputGestureData = builder.build()
+
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData)
+ testLooper.dispatchAll()
+
+ // Reinitialize the gesture controller simulating a login/logout for the user.
+ setupKeyGestureController()
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
+ assertEquals(
+ "Test: $test doesn't produce correct number of saved input gestures",
+ 1,
+ savedInputGestures.size
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct input gesture data", inputGestureData,
+ InputGestureData(savedInputGestures[0])
+ )
+ }
+
class TouchpadTestData(
val name: String,
val touchpadGestureType: Int,
@@ -1502,6 +1574,39 @@ class KeyGestureControllerTests {
keyGestureController.unregisterKeyGestureHandler(handler, 0)
}
+ @Test
+ @Parameters(method = "customTouchpadGesturesTestArguments")
+ fun testCustomTouchpadGesturesSavedAndLoadedByController(test: TouchpadTestData) {
+ val userId = 10
+ setupKeyGestureController()
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType))
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ val inputGestureData = builder.build()
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData)
+ testLooper.dispatchAll()
+
+ // Reinitialize the gesture controller simulating a login/logout for the user.
+ setupKeyGestureController()
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
+ assertEquals(
+ "Test: $test doesn't produce correct number of saved input gestures",
+ 1,
+ savedInputGestures.size
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct input gesture data", inputGestureData,
+ InputGestureData(savedInputGestures[0])
+ )
+ }
+
private fun testKeyGestureInternal(test: TestData) {
val handledEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 938e2f8a3611..644d5a0679de 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -25,6 +25,7 @@ import android.hardware.input.IKeyboardBacklightListener
import android.hardware.input.IKeyboardBacklightState
import android.hardware.input.InputManager
import android.hardware.lights.Light
+import android.os.SystemProperties
import android.os.UEventObserver
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
@@ -32,16 +33,13 @@ import android.view.InputDevice
import android.util.TypedValue
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.R
+import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
import com.android.test.input.MockInputManagerRule
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@@ -56,7 +54,6 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
private fun createKeyboard(deviceId: Int): InputDevice =
InputDevice.Builder()
@@ -101,7 +98,8 @@ class KeyboardBacklightControllerTests {
}
@get:Rule
- val rule = MockitoJUnit.rule()!!
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!!
@get:Rule
val inputManagerRule = MockInputManagerRule()
@@ -113,7 +111,6 @@ class KeyboardBacklightControllerTests {
private lateinit var resources: Resources
private lateinit var keyboardBacklightController: KeyboardBacklightController
private lateinit var context: Context
- private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
private var lightColorMap: HashMap<Int, Int> = HashMap()
private var lastBacklightState: KeyboardBacklightState? = null
@@ -124,21 +121,8 @@ class KeyboardBacklightControllerTests {
fun setup() {
context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
`when`(context.resources).thenReturn(resources)
- dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
- override fun openRead(): InputStream? {
- throw FileNotFoundException()
- }
-
- override fun startWrite(): FileOutputStream? {
- throw IOException()
- }
-
- override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
- })
testLooper = TestLooper()
setupConfig()
- keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
- testLooper.looper, FakeAnimatorFactory(), uEventManager)
val inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
@@ -155,6 +139,7 @@ class KeyboardBacklightControllerTests {
sysfsNodeChanges++
}
}
+
private fun setupConfig() {
val brightnessValues = intArrayOf(100, 200, 0)
val decreaseThresholds = intArrayOf(-1, 900, 1900)
@@ -180,271 +165,166 @@ class KeyboardBacklightControllerTests {
Unit
}
}
- @Test
- fun testKeyboardBacklightIncrementDecrement() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
- }
- }
- @Test
- fun testKeyboardWithoutBacklight() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
- val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithoutBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
- }
+ private fun setupController() {
+ keyboardBacklightController = KeyboardBacklightController(context, native,
+ testLooper.looper, FakeAnimatorFactory(), uEventManager)
}
@Test
- fun testKeyboardWithMultipleLight() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
- listOf(
- keyboardBacklight,
- keyboardInputLight
- )
- )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
- assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
- assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
- }
+ fun testKeyboardBacklightIncrementDecrement() {
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
}
@Test
- fun testRestoreBacklightOnInputDeviceAdded() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
- for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the " +
- "data store",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
- }
- }
+ fun testKeyboardWithoutBacklight() {
+ setupController()
+ val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+ val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
}
@Test
- fun testRestoreBacklightOnInputDeviceChanged() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertTrue(
- "Keyboard backlight should not be changed until its added",
- lightColorMap.isEmpty()
+ fun testKeyboardWithMultipleLight() {
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
+ listOf(
+ keyboardBacklight,
+ keyboardInputLight
)
+ )
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+ assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+ assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
}
@Test
fun testKeyboardBacklight_registerUnregisterListener() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Register backlight listener
- val listener = KeyboardBacklightListener()
- keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
-
- lastBacklightState = null
- keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
- testLooper.dispatchNext()
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ // Register backlight listener
+ val listener = KeyboardBacklightListener()
+ keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+
+ lastBacklightState = null
+ keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+ testLooper.dispatchNext()
- assertEquals(
- "Backlight state device Id should be $DEVICE_ID",
- DEVICE_ID,
- lastBacklightState!!.deviceId
- )
- assertEquals(
- "Backlight state brightnessLevel should be 1",
- 1,
- lastBacklightState!!.brightnessLevel
- )
- assertEquals(
- "Backlight state maxBrightnessLevel should be $maxLevel",
- maxLevel,
- lastBacklightState!!.maxBrightnessLevel
- )
- assertEquals(
- "Backlight state isTriggeredByKeyPress should be true",
- true,
- lastBacklightState!!.isTriggeredByKeyPress
- )
+ assertEquals(
+ "Backlight state device Id should be $DEVICE_ID",
+ DEVICE_ID,
+ lastBacklightState!!.deviceId
+ )
+ assertEquals(
+ "Backlight state brightnessLevel should be 1",
+ 1,
+ lastBacklightState!!.brightnessLevel
+ )
+ assertEquals(
+ "Backlight state maxBrightnessLevel should be $maxLevel",
+ maxLevel,
+ lastBacklightState!!.maxBrightnessLevel
+ )
+ assertEquals(
+ "Backlight state isTriggeredByKeyPress should be true",
+ true,
+ lastBacklightState!!.isTriggeredByKeyPress
+ )
- // Unregister listener
- keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+ // Unregister listener
+ keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
- lastBacklightState = null
- incrementKeyboardBacklight(DEVICE_ID)
+ lastBacklightState = null
+ incrementKeyboardBacklight(DEVICE_ID)
- assertNull("Listener should not receive any updates", lastBacklightState)
- }
+ assertNull("Listener should not receive any updates", lastBacklightState)
}
@Test
fun testKeyboardBacklight_userActivity() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertNotEquals(
+ "Keyboard backlight level should be incremented to a non-zero value",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be turned off after inactivity",
- 0,
- lightColorMap[LIGHT_ID]
- )
- }
+ testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be turned off after inactivity",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testKeyboardBacklight_displayOnOff() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+
+ val currentValue = lightColorMap[LIGHT_ID]
+ assertNotEquals(
+ "Keyboard backlight level should be incremented to a non-zero value",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data " +
- "store when display turned on",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be turned off after display is turned off",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be turned off after display is turned off",
- 0,
- lightColorMap[LIGHT_ID]
- )
- }
+ keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be turned on after display is turned on",
+ currentValue,
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
+ setupController()
var counter = sysfsNodeChanges
keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
"ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
@@ -504,260 +384,160 @@ class KeyboardBacklightControllerTests {
@Test
@UiThreadTest
fun testKeyboardBacklightAnimation_onChangeLevels() {
- KeyboardBacklightFlags(
- animationEnabled = true,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Should start animation from level 0",
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
- lastAnimationValues[0]
- )
- assertEquals(
- "Should start animation to level 1",
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
- lastAnimationValues[1]
- )
+ ExtendedMockito.doReturn("true").`when` {
+ SystemProperties.get(eq("persist.input.keyboard.backlight_animation.enabled"))
}
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Should start animation from level 0",
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
+ lastAnimationValues[0]
+ )
+ assertEquals(
+ "Should start animation to level 1",
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
+ lastAnimationValues[1]
+ )
}
@Test
fun testKeyboardBacklightPreferredLevels() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
- suggestedLevels)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- suggestedLevels)
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, suggestedLevels)
}
@Test
fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
- suggestedLevels)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
}
@Test
fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val suggestedLevels = intArrayOf(22, 63, 135, 196)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
- suggestedLevels)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Framework will add the lowest and maximum levels if not provided via config
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- intArrayOf(0, 22, 63, 135, 196, 255))
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(22, 63, 135, 196)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ // Framework will add the lowest and maximum levels if not provided via config
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ intArrayOf(0, 22, 63, 135, 196, 255))
}
@Test
fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
- suggestedLevels)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID))
- .thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Framework will drop out of bound levels in the config
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- intArrayOf(0, 22, 63, 135, 196, 255))
- }
- }
-
- @Test
- fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertNull(
- "Keyboard backlight level should not be restored to the saved level",
- lightColorMap[LIGHT_ID]
- )
- }
- }
-
- @Test
- fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- incrementKeyboardBacklight(DEVICE_ID)
- assertFalse(
- "Light value should not be backed up if ambient control is enabled",
- dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor, LIGHT_ID
- ).isPresent
- )
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ // Framework will drop out of bound levels in the config
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ intArrayOf(0, 22, 63, 135, 196, 255))
}
@Test
fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- sendAmbientBacklightValue(1)
- assertEquals(
- "Light value should be changed to ambient provided value",
- Color.argb(1, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(1)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(1, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- incrementKeyboardBacklight(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value for level after increment post Ambient change is mismatched",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ assertEquals(
+ "Light value for level after increment post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- sendAmbientBacklightValue(254)
- assertEquals(
- "Light value should be changed to ambient provided value",
- Color.argb(254, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(254)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(254, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- decrementKeyboardBacklight(DEVICE_ID)
+ decrementKeyboardBacklight(DEVICE_ID)
- val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
- assertEquals(
- "Light value for level after decrement post Ambient change is mismatched",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+ assertEquals(
+ "Light value for level after decrement post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
- .thenReturn(keyboardWithBacklight)
- `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value should be changed to the first level",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Light value should be changed to the first level",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- sendAmbientBacklightValue(100)
- assertNotEquals(
- "Light value should not change based on ambient changes after manual changes",
- Color.argb(100, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ sendAmbientBacklightValue(100)
+ assertNotEquals(
+ "Light value should not change based on ambient changes after manual changes",
+ Color.argb(100, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
private fun assertIncrementDecrementForLevels(
@@ -774,11 +554,6 @@ class KeyboardBacklightControllerTests {
Color.argb(expectedLevels[level], 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- expectedLevels[level],
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
// Increment above max level
@@ -788,11 +563,6 @@ class KeyboardBacklightControllerTests {
Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for max level must be correctly stored in the datastore",
- MAX_BRIGHTNESS,
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
for (level in expectedLevels.size - 2 downTo 0) {
decrementKeyboardBacklight(deviceId)
@@ -801,11 +571,6 @@ class KeyboardBacklightControllerTests {
Color.argb(expectedLevels[level], 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- expectedLevels[level],
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
// Decrement below min level
@@ -815,11 +580,6 @@ class KeyboardBacklightControllerTests {
Color.argb(0, 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for min level must be correctly stored in the datastore",
- 0,
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
@@ -862,23 +622,6 @@ class KeyboardBacklightControllerTests {
val isTriggeredByKeyPress: Boolean
)
- private inner class KeyboardBacklightFlags constructor(
- animationEnabled: Boolean,
- customLevelsEnabled: Boolean,
- ambientControlEnabled: Boolean
- ) : AutoCloseable {
- init {
- InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
- InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
- InputFeatureFlagProvider
- .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
- }
-
- override fun close() {
- InputFeatureFlagProvider.clearOverrides()
- }
- }
-
private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
lastAnimationValues[0] = from
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
new file mode 100644
index 000000000000..67d4397afe7d
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <certificateTransparency enabled="true" />
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_user.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_user_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_inline.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_inline_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
new file mode 100644
index 000000000000..c35fd71c3178
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain.android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 0494f174f191..c6fe06858e3f 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -111,7 +111,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
private NetworkSecurityConfig getSystemStoreConfig() {
return new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
}
@@ -141,7 +142,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -159,7 +161,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -178,7 +181,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), true, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -245,7 +249,8 @@ public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index 81e05c1d4e42..542465d62a66 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -502,4 +502,47 @@ public class XmlConfigTests extends AndroidTestCase {
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
+
+ public void testCertificateTransparencyDomainConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_domains,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
+
+ public void testCertificateTransparencyUserConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_users,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
}
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index af87bf724a30..49616c30b784 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -83,6 +83,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -112,6 +113,7 @@ public class CrashRecoveryTest {
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -141,6 +143,7 @@ public class CrashRecoveryTest {
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -231,31 +234,37 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
}
@@ -272,6 +281,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -281,6 +291,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -289,6 +300,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
}
@@ -305,18 +317,21 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -326,24 +341,28 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -352,6 +371,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -362,6 +382,7 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -379,12 +400,14 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -394,6 +417,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -402,6 +426,7 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -412,6 +437,7 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -739,14 +765,14 @@ public class CrashRecoveryTest {
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
- watchdog.registerHealthObserver(rollbackObserver);
+ watchdog.registerHealthObserver(rollbackObserver, mTestExecutor);
return rollbackObserver;
}
RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
setCrashRecoveryPropRescueBootCount(0);
RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
assertFalse(RescueParty.isRebootPropertySet());
- watchdog.registerHealthObserver(rescuePartyObserver);
+ watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
return rescuePartyObserver;
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 5a8a6bedf9eb..c64dc7296f0a 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -53,6 +53,7 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
+import android.util.Slog;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -88,6 +89,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -119,6 +121,7 @@ public class PackageWatchdogTest {
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -155,6 +158,7 @@ public class PackageWatchdogTest {
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -226,7 +230,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -242,8 +247,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -260,7 +267,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -276,8 +284,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -294,7 +304,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -310,8 +321,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -330,13 +343,14 @@ public class PackageWatchdogTest {
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
// Start observing APP_A
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time half-way
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
// Start observing APP_A again
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time such that it should have expired were it not for the second observation
moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -358,15 +372,17 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer1, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer2, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Then advance time and run IO Handler so file is saved
mTestLooper.dispatchAll();
// Then start a new watchdog
PackageWatchdog watchdog2 = createWatchdog();
// Then resume observer1 and observer2
- watchdog2.registerHealthObserver(observer1);
- watchdog2.registerHealthObserver(observer2);
+ watchdog2.registerHealthObserver(observer1, mTestExecutor);
+ watchdog2.registerHealthObserver(observer2, mTestExecutor);
raiseFatalFailureAndDispatch(watchdog2,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -374,6 +390,7 @@ public class PackageWatchdogTest {
// We should receive failed packages as expected to ensure observers are persisted and
// resumed correctly
+ mTestLooper.dispatchAll();
assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
@@ -387,8 +404,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
@@ -414,9 +433,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -448,7 +468,8 @@ public class PackageWatchdogTest {
}
};
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -477,13 +498,17 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -523,13 +548,17 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -577,8 +606,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -641,8 +672,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -709,8 +742,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -731,8 +766,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -762,8 +799,10 @@ public class PackageWatchdogTest {
// Start observing with explicit health checks for APP_A and APP_B respectively
// with observer1 and observer2
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -779,7 +818,8 @@ public class PackageWatchdogTest {
// Observer3 didn't exist when we got the explicit health check above, so
// it starts out with a non-passing explicit health check and has to wait for a pass
// otherwise it would be notified of APP_A failure on expiry
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -809,8 +849,9 @@ public class PackageWatchdogTest {
// Start observing with explicit health checks for APP_A and APP_B
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -846,7 +887,7 @@ public class PackageWatchdogTest {
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
// Start observing APP_A and APP_C; only APP_C has support for explicit health checks
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
// Run handler so requests/cancellations are dispatched to the controller
mTestLooper.dispatchAll();
@@ -877,7 +918,8 @@ public class PackageWatchdogTest {
// package observation duration == LONG_DURATION
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
// Then APP_A has exceeded health check duration
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -908,7 +950,8 @@ public class PackageWatchdogTest {
// package observation duration == SHORT_DURATION / 2
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
// Forward time to expire the observation duration
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -981,7 +1024,7 @@ public class PackageWatchdogTest {
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1001,7 +1044,7 @@ public class PackageWatchdogTest {
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1022,7 +1065,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Fail APP_A below the threshold which should not trigger package failures
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1050,7 +1094,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
@@ -1075,15 +1120,16 @@ public class PackageWatchdogTest {
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_beforeExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
// Note: Don't move too close to the expiration time otherwise the handler will be thrashed
// by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
// small timeouts.
@@ -1097,15 +1143,16 @@ public class PackageWatchdogTest {
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_afterExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1127,7 +1174,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
// Raise 2 failures at t=0 and t=900 respectively
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1154,8 +1202,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1174,7 +1224,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1194,7 +1245,8 @@ public class PackageWatchdogTest {
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(true);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1212,7 +1264,8 @@ public class PackageWatchdogTest {
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(false);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1223,13 +1276,15 @@ public class PackageWatchdogTest {
/** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
@Test
public void testBootLoopDetection_meetsThreshold() {
+ Slog.w("hrm1243", "I should definitely be here try 1 ");
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1237,10 +1292,11 @@ public class PackageWatchdogTest {
public void testBootLoopDetection_meetsThresholdRecoverability() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1252,10 +1308,11 @@ public class PackageWatchdogTest {
public void testBootLoopDetection_doesNotMeetThreshold() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1268,10 +1325,11 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1286,11 +1344,12 @@ public class PackageWatchdogTest {
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1302,11 +1361,12 @@ public class PackageWatchdogTest {
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1319,7 +1379,7 @@ public class PackageWatchdogTest {
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
watchdog.noteBoot();
@@ -1333,7 +1393,7 @@ public class PackageWatchdogTest {
watchdog.noteBoot();
}
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1342,7 +1402,7 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
watchdog.noteBoot();
}
@@ -1358,7 +1418,7 @@ public class PackageWatchdogTest {
for (int i = 0; i < 4; i++) {
watchdog.noteBoot();
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1370,7 +1430,8 @@ public class PackageWatchdogTest {
public void testNullFailedPackagesList() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1388,18 +1449,18 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog(testController, true);
TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(testObserver1);
- watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.registerHealthObserver(testObserver2);
- watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
- watchdog.registerHealthObserver(testObserver3);
- watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
mTestLooper.dispatchAll();
watchdog.unregisterHealthObserver(testObserver1);
@@ -1431,14 +1492,15 @@ public class PackageWatchdogTest {
public void testFailureHistoryIsPreserved() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEmpty();
- watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1453,7 +1515,8 @@ public class PackageWatchdogTest {
public void testMitigationSlidingWindow() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A),
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
@@ -1895,6 +1958,7 @@ public class PackageWatchdogTest {
}
public boolean onExecuteBootLoopMitigation(int level) {
+ Slog.w("hrm1243", "I'm here " + level);
mMitigatedBootLoop = true;
mBootMitigationCounts.add(level);
return true;
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
new file mode 100644
index 000000000000..9e15ac41d84b
--- /dev/null
+++ b/tests/broadcasts/unit/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+ // 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"],
+ default_team: "trendy_team_framework_backstage_power",
+}
+
+android_test {
+ name: "BroadcastUnitTests",
+ srcs: ["src/**/*.java"],
+ defaults: [
+ "modules-utils-extended-mockito-rule-defaults",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "truth",
+ "flag-junit",
+ "android.app.flags-aconfig-java",
+ "junit-params",
+ ],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/tests/broadcasts/unit/AndroidManifest.xml
index f8cd8f0b9b44..61eb230f7957 100644
--- a/services/tests/security/intrusiondetection/res/xml/device_admin.xml
+++ b/tests/broadcasts/unit/AndroidManifest.xml
@@ -14,5 +14,14 @@
limitations under the License.
-->
-<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
-</device-admin>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.broadcasts.unit" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.broadcasts.unit"
+ android:label="Broadcasts Unit Tests"/>
+</manifest> \ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
new file mode 100644
index 000000000000..b91e4783b69e
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- 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.
+-->
+<configuration description="Runs Broadcasts tests">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="BroadcastUnitTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BroadcastUnitTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.broadcasts.unit" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration> \ No newline at end of file
diff --git a/tests/broadcasts/unit/OWNERS b/tests/broadcasts/unit/OWNERS
new file mode 100644
index 000000000000..f1e450b7e5f9
--- /dev/null
+++ b/tests/broadcasts/unit/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS \ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
new file mode 100644
index 000000000000..b920e2586c86
--- /dev/null
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "BroadcastUnitTests"
+ }
+ ]
+}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
new file mode 100644
index 000000000000..ad032fb2fba6
--- /dev/null
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.IpcDataCache;
+import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.annotations.Keep;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(JUnitParamsRunner.class)
+public class BroadcastStickyCacheTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(IpcDataCache.class)
+ .mockStatic(ActivityManager.class)
+ .build();
+
+ @Mock
+ private IActivityManager mActivityManagerMock;
+
+ @Mock
+ private IApplicationThread mIApplicationThreadMock;
+
+ @Keep
+ private static Object stickyBroadcastList() {
+ return BroadcastStickyCache.STICKY_BROADCAST_ACTIONS;
+ }
+
+ @Before
+ public void setUp() {
+ BroadcastStickyCache.clearCacheForTest();
+
+ doNothing().when(() -> IpcDataCache.invalidateCache(anyString(), anyString()));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_flagDisabled_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_nullFilter_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(null));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_filterWithoutAction_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter()));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_filterWithoutStickyBroadcastAction_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BOOT_COMPLETED)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_flagDisabled_cacheNotInvalidated() {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
+ BroadcastStickyCache.invalidateCache(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+ times(0));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_broadcastNotSticky_cacheNotInvalidated() {
+ BroadcastStickyCache.invalidateCache(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), anyString()),
+ times(0));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_withStickyBroadcast_cacheInvalidated() {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+ Intent.ACTION_BATTERY_CHANGED);
+
+ BroadcastStickyCache.invalidateCache(Intent.ACTION_BATTERY_CHANGED);
+
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+ times(1));
+ }
+
+ @Test
+ public void invalidateAllCaches_cacheInvalidated() {
+ BroadcastStickyCache.invalidateAllCaches();
+
+ for (int i = BroadcastStickyCache.sActionApiNameMap.size() - 1; i > -1; i--) {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.valueAt(i);
+ ExtendedMockito.verify(() -> IpcDataCache.invalidateCache(anyString(),
+ eq(apiName)), times(1));
+ }
+ }
+
+ @Test
+ @Parameters(method = "stickyBroadcastList")
+ public void getIntent_createNewCache_verifyRegisterReceiverIsCalled(String action)
+ throws RemoteException {
+ setActivityManagerMock(action);
+ final IntentFilter filter = new IntentFilter(action);
+ final Intent intent = queryIntent(filter);
+
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), action);
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(filter), anyString(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void getIntent_querySameValueTwice_verifyRegisterReceiverIsCalledOnce()
+ throws RemoteException {
+ setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+ final Intent cachedIntent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+ assertNotNull(cachedIntent);
+ assertEquals(cachedIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ any(), anyString(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void getIntent_querySameActionWithDifferentFilter_verifyRegisterReceiverCalledTwice()
+ throws RemoteException {
+ setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final Intent intent = queryIntent(filter);
+
+ final IntentFilter newFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ newFilter.addDataScheme("file");
+ final Intent newIntent = queryIntent(newFilter);
+
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+ assertNotNull(newIntent);
+ assertEquals(newIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(filter), anyString(), anyInt(), anyInt());
+
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(newFilter), anyString(), anyInt(), anyInt());
+ }
+
+ private Intent queryIntent(IntentFilter filter) {
+ return BroadcastStickyCache.getIntent(
+ mIApplicationThreadMock,
+ "android",
+ "android",
+ filter,
+ "system",
+ 0,
+ 0
+ );
+ }
+
+ private void setActivityManagerMock(String action) throws RemoteException {
+ when(ActivityManager.getService()).thenReturn(mActivityManagerMock);
+ when(mActivityManagerMock.registerReceiverWithFeature(any(), anyString(),
+ anyString(), anyString(), any(), any(), anyString(), anyInt(),
+ anyInt())).thenReturn(new Intent(action));
+ }
+}
diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml
index 25092b52e2b6..c293589bdbaf 100644
--- a/tests/graphics/SilkFX/AndroidManifest.xml
+++ b/tests/graphics/SilkFX/AndroidManifest.xml
@@ -23,13 +23,12 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application android:label="SilkFX"
- android:theme="@style/Theme.UsefulDefault">
+ android:theme="@android:style/Theme.Material">
<activity android:name=".Main"
android:label="SilkFX Demos"
android:banner="@drawable/background1"
- android:exported="true"
- android:theme="@style/Theme.UsefulDefault">
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
index f13c0883cb01..27eca82dcb23 100644
--- a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
@@ -13,161 +13,168 @@
~ WITHOUT 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"
+-->
+<FrameLayout
+ 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:id="@+id/background"
- android:layout_width="390dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:padding="15dp"
- android:orientation="vertical"
+ android:fitsSystemWindows="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
tools:context=".materials.BackgroundBlurActivity">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:padding="10dp"
- android:textColor="#ffffffff"
- android:text="Hello blurry world!"/>
-
<LinearLayout
- android:layout_width="match_parent"
+ android:id="@+id/background"
+ android:layout_width="390dp"
android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Background blur"/>
+ android:layout_gravity="center"
+ android:padding="15dp"
+ android:orientation="vertical">
- <SeekBar
- android:id="@+id/set_background_blur"
- android:min="0"
- android:max="300"
- android:layout_width="160dp"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/background_blur_radius"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#ffffffff"
- android:ems="3"
- android:gravity="center"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
<TextView
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
+ android:gravity="center_horizontal"
+ android:padding="10dp"
android:textColor="#ffffffff"
- android:text="Background alpha"/>
+ android:text="Hello blurry world!"/>
- <SeekBar
- android:id="@+id/set_background_alpha"
- android:min="0"
- android:max="100"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/background_alpha"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#ffffffff"
- android:ems="3"
- android:gravity="center"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Blur behind"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Background blur"/>
- <SeekBar
- android:id="@+id/set_blur_behind"
- android:min="0"
- android:max="300"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/blur_behind_radius"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="#ffffffff"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:ems="3"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
+ <SeekBar
+ android:id="@+id/set_background_blur"
+ android:min="0"
+ android:max="300"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/background_blur_radius"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ffffffff"
+ android:ems="3"
+ android:gravity="center"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Dim amount"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Background alpha"/>
- <SeekBar
- android:id="@+id/set_dim_amount"
- android:min="0"
- android:max="100"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/dim_amount"
- android:layout_width="wrap_content"
+ <SeekBar
+ android:id="@+id/set_background_alpha"
+ android:min="0"
+ android:max="100"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/background_alpha"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ffffffff"
+ android:ems="3"
+ android:gravity="center"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="#ffffffff"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:ems="3"
- android:text="TODO"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="5dp"
- android:orientation="vertical"
- android:gravity="center">
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Blur behind"/>
- <Button
- android:id="@+id/toggle_blur_enabled"
+ <SeekBar
+ android:id="@+id/set_blur_behind"
+ android:min="0"
+ android:max="300"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/blur_behind_radius"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="#ffffffff"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:ems="3"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="Disable blur"
- android:onClick="toggleForceBlurDisabled"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Dim amount"/>
- <Button
- android:id="@+id/toggle_battery_saving_mode"
+ <SeekBar
+ android:id="@+id/set_dim_amount"
+ android:min="0"
+ android:max="100"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/dim_amount"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="#ffffffff"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:ems="3"
+ android:text="TODO"/>
+ </LinearLayout>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="TODO"
- android:onClick="toggleBatterySavingMode"/>
- </LinearLayout>
- <requestFocus/>
+ android:layout_gravity="center"
+ android:layout_marginTop="5dp"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <Button
+ android:id="@+id/toggle_blur_enabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Disable blur"
+ android:onClick="toggleForceBlurDisabled"/>
-</LinearLayout>
+ <Button
+ android:id="@+id/toggle_battery_saving_mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="TODO"
+ android:onClick="toggleBatterySavingMode"/>
+ </LinearLayout>
+ <requestFocus/>
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/tests/graphics/SilkFX/res/layout/activity_glass.xml b/tests/graphics/SilkFX/res/layout/activity_glass.xml
index aa09f276d5c8..d591fc4606b0 100644
--- a/tests/graphics/SilkFX/res/layout/activity_glass.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_glass.xml
@@ -19,6 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
tools:context=".MainActivity">
<ImageView
@@ -300,4 +301,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
-</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
index c0c0bab8a605..9b2b0c818a8e 100644
--- a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
+++ b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
@@ -19,6 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_margin="8dp"
android:orientation="vertical">
<TextView
@@ -61,4 +62,4 @@
</LinearLayout>
-</com.android.test.silkfx.common.ColorModeControls> \ No newline at end of file
+</com.android.test.silkfx.common.ColorModeControls>
diff --git a/tests/graphics/SilkFX/res/layout/common_base.xml b/tests/graphics/SilkFX/res/layout/common_base.xml
index c0eaf9bc1476..ce6d850af1bc 100644
--- a/tests/graphics/SilkFX/res/layout/common_base.xml
+++ b/tests/graphics/SilkFX/res/layout/common_base.xml
@@ -18,6 +18,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">
<include layout="@layout/color_mode_controls" />
@@ -26,4 +27,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/graphics/SilkFX/res/layout/hdr_glows.xml b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
index b6050645866a..f1e553a3df23 100644
--- a/tests/graphics/SilkFX/res/layout/hdr_glows.xml
+++ b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
@@ -18,6 +18,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">
<include layout="@layout/color_mode_controls" />
@@ -48,4 +49,4 @@
android:layout_height="50dp"
android:layout_margin="8dp" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml
index 4dd626dfb8f5..75506978024b 100644
--- a/tests/graphics/SilkFX/res/values/style.xml
+++ b/tests/graphics/SilkFX/res/values/style.xml
@@ -16,21 +16,16 @@
-->
<!-- Styles for immersive actions UI. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog">
+ <style name="Theme.BackgroundBlurTheme" parent="Theme.AppCompat.Dialog">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBlurBehindEnabled">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowElevation">0dp</item>
<item name="buttonStyle">@style/AppTheme.Button</item>
<item name="colorAccent">#bbffffff</item>
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="AppTheme.Button" parent="Widget.AppCompat.Button">
<item name="android:textColor">#ffffffff</item>
</style>
- <style name="Theme.UsefulDefault" parent="android:Theme.Material">
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
- </style>
-
</resources>
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index 6b6d3b8d3d12..ad7cde44bb35 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -72,6 +72,7 @@ class Main : Activity() {
super.onCreate(savedInstanceState)
val list = ExpandableListView(this)
+ list.setFitsSystemWindows(true)
setContentView(list)
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index b16ba15a6867..51a300bff7ea 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -14,21 +14,24 @@ package {
android_test {
name: "FrameworksVcnTests",
+ // For access hidden connectivity methods in tests
+ defaults: ["framework-connectivity-test-defaults"],
srcs: [
"java/**/*.java",
"java/**/*.kt",
],
platform_apis: true,
- defaults: ["framework-connectivity-test-defaults"],
test_suites: ["device-tests"],
certificate: "platform",
static_libs: [
+ "android.net.vcn.flags-aconfig-java-export",
"androidx.test.rules",
"frameworks-base-testutils",
"framework-protos",
"mockito-target-minus-junit4",
"net-tests-utils",
"platform-test-annotations",
+ "service-connectivity-b-pre-jarjar",
"services.core",
"service-connectivity-tiramisu-pre-jarjar",
"flag-junit",
diff --git a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
index e9e70783ebe9..47638b002f37 100644
--- a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
@@ -22,10 +22,10 @@ import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.vcn.util.MtuUtils.getMtu;
import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-import static com.android.server.vcn.util.MtuUtils.getMtu;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
index 9c6d85238b77..c84e60086b37 100644
--- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.vcn.util;
+package android.net.vcn.util;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4ab8e6abbbef..26a2a0636792 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -77,6 +77,8 @@ import android.net.vcn.VcnConfigTest;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -99,8 +101,6 @@ import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
-import com.android.server.vcn.util.PersistableBundleUtils;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import org.junit.Before;
import org.junit.Rule;
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index f1f74bca2ef9..77f82f0d8cf4 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -19,6 +19,7 @@ package com.android.server.vcn;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -26,7 +27,6 @@ import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -55,7 +55,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.vcn.VcnManager;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.os.test.TestLooper;
@@ -72,6 +71,8 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.HandlerExecutor;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 20b7f1f14691..74db6a5211a0 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -69,6 +69,7 @@ import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.MtuUtils;
import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
@@ -79,7 +80,6 @@ import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
-import com.android.server.vcn.util.MtuUtils;
import org.junit.Before;
import org.junit.Test;
@@ -659,7 +659,6 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
private void verifySetSafeModeAlarm(
boolean safeModeEnabledByCaller,
- boolean safeModeConfigFlagEnabled,
boolean expectingSafeModeEnabled)
throws Exception {
final VcnGatewayConnectionConfig config =
@@ -670,7 +669,6 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
mock(VcnGatewayConnection.Dependencies.class);
setUpWakeupMessage(
mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM, deps);
- doReturn(safeModeConfigFlagEnabled).when(mFeatureFlags).safeModeConfig();
final VcnGatewayConnection connection =
new VcnGatewayConnection(
@@ -694,37 +692,19 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
}
@Test
- public void testSafeModeEnabled_configFlagEnabled() throws Exception {
+ public void testSafeModeEnabled() throws Exception {
verifySetSafeModeAlarm(
true /* safeModeEnabledByCaller */,
- true /* safeModeConfigFlagEnabled */,
true /* expectingSafeModeEnabled */);
}
@Test
- public void testSafeModeEnabled_configFlagDisabled() throws Exception {
- verifySetSafeModeAlarm(
- true /* safeModeEnabledByCaller */,
- false /* safeModeConfigFlagEnabled */,
- true /* expectingSafeModeEnabled */);
- }
-
- @Test
- public void testSafeModeDisabled_configFlagEnabled() throws Exception {
+ public void testSafeModeDisabled() throws Exception {
verifySetSafeModeAlarm(
false /* safeModeEnabledByCaller */,
- true /* safeModeConfigFlagEnabled */,
false /* expectingSafeModeEnabled */);
}
- @Test
- public void testSafeModeDisabled_configFlagDisabled() throws Exception {
- verifySetSafeModeAlarm(
- false /* safeModeEnabledByCaller */,
- false /* safeModeConfigFlagEnabled */,
- true /* expectingSafeModeEnabled */);
- }
-
private Consumer<VcnNetworkAgent> setupNetworkAndGetUnwantedCallback() {
triggerChildOpened();
mTestLooper.dispatchAll();
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 613b92616707..b9fe76a24d20 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -25,13 +25,13 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS;
import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index 441a4ae6d9b6..5db02e376f3d 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -19,11 +19,11 @@ package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index d85c5150f53f..4f34f9f8f74c 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -23,13 +23,13 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTR
import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS;
import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index 1d6872195e81..a315b0690ec5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -17,9 +17,9 @@
package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
index 03610128d269..c438163948fc 100644
--- a/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
+++ b/tools/processors/property_cache/src/java/android/processor/property_cache/CachedPropertyProcessor.java
@@ -30,6 +30,7 @@ import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
@@ -42,6 +43,11 @@ public class CachedPropertyProcessor extends AbstractProcessor {
new IpcDataCacheComposer();
@Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
public Set<String> getSupportedAnnotationTypes() {
return new HashSet<String>(
ImmutableSet.of(CachedPropertyDefaults.class.getCanonicalName()));